diff --git a/client/src/main/java/ctbrec/ui/Player.java b/client/src/main/java/ctbrec/ui/Player.java index d7da07ab..b3e3dbb9 100644 --- a/client/src/main/java/ctbrec/ui/Player.java +++ b/client/src/main/java/ctbrec/ui/Player.java @@ -12,8 +12,8 @@ import ctbrec.ui.controls.Dialogs; import ctbrec.ui.event.PlayerStartedEvent; import ctbrec.variableexpansion.ModelVariableExpander; import javafx.scene.Scene; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import javax.xml.bind.JAXBException; import java.io.File; @@ -29,8 +29,8 @@ import java.util.Iterator; import java.util.List; import java.util.concurrent.ExecutionException; +@Slf4j public class Player { - private static final Logger LOG = LoggerFactory.getLogger(Player.class); private static PlayerThread playerThread; private static Scene scene; @@ -47,7 +47,7 @@ public class Player { playerThread = new PlayerThread(rec); return true; } catch (Exception e1) { - LOG.error("Couldn't start player", e1); + log.error("Couldn't start player", e1); return false; } } @@ -81,11 +81,11 @@ public class Player { } } catch (InterruptedException e) { Thread.currentThread().interrupt(); - LOG.error("Couldn't get stream information for model {}", model, e); + log.error("Couldn't get stream information for model {}", model, e); Dialogs.showError(scene, "Couldn't determine stream URL", e.getLocalizedMessage(), e); return false; } catch (Exception e) { - LOG.error("Couldn't get stream information for model {}", model, e); + log.error("Couldn't get stream information for model {}", model, e); Dialogs.showError(scene, "Couldn't determine stream URL", e.getLocalizedMessage(), e); return false; } @@ -98,6 +98,7 @@ public class Player { } private static class PlayerThread extends Thread { + @Getter private boolean running = false; private Process playerProcess; private Recording rec; @@ -134,9 +135,9 @@ public class Player { } else if (model != null) { url = getPlaylistUrl(model); } - LOG.debug("Playing {}", url); + log.debug("Playing {}", url); String[] cmdline = createCmdline(url, model); - LOG.debug("Player command line: {}", Arrays.toString(cmdline)); + log.debug("Player command line: {}", Arrays.toString(cmdline)); playerProcess = rt.exec(cmdline); } @@ -152,13 +153,13 @@ public class Player { err.start(); playerProcess.waitFor(); - LOG.debug("Media player finished."); + log.debug("Media player finished."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - LOG.error("Error in player thread", e); + log.error("Error in player thread", e); Dialogs.showError(scene, "Playback failed", "Couldn't start playback", e); } catch (Exception e) { - LOG.error("Error in player thread", e); + log.error("Error in player thread", e); Dialogs.showError(scene, "Playback failed", "Couldn't start playback", e); } running = false; @@ -172,8 +173,8 @@ public class Player { if (maxRes > 0 && !sources.isEmpty()) { for (Iterator iterator = sources.iterator(); iterator.hasNext(); ) { StreamSource streamSource = iterator.next(); - if (streamSource.height > 0 && maxRes < streamSource.height) { - LOG.trace("Res too high {} > {}", streamSource.height, maxRes); + if (streamSource.getHeight() > 0 && maxRes < streamSource.getHeight()) { + log.trace("Res too high {} > {}", streamSource.getHeight(), maxRes); iterator.remove(); } } @@ -181,7 +182,7 @@ public class Player { if (sources.isEmpty()) { throw new NoStreamFoundException("No stream left in playlist, because player resolution is set to " + maxRes); } else { - LOG.debug("{} selected {}", model.getName(), sources.get(sources.size() - 1)); + log.debug("{} selected {}", model.getName(), sources.get(sources.size() - 1)); best = sources.get(sources.size() - 1); } return best.getMediaPlaylistUrl(); @@ -226,10 +227,6 @@ public class Player { return recUrl; } - public boolean isRunning() { - return running; - } - public void stopThread() { if (playerProcess != null) { playerProcess.destroy(); diff --git a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java index cbf04842..aefad270 100644 --- a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java +++ b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java @@ -33,6 +33,7 @@ import javafx.scene.control.TextInputDialog; import javafx.scene.layout.*; import javafx.scene.paint.Color; import javafx.util.Duration; +import lombok.Getter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -128,10 +129,10 @@ public class SettingsTab extends Tab implements TabSelectionListener { private SimpleStringProperty dateTimeFormat; private final VariablePlayGroundDialogFactory variablePlayGroundDialogFactory = new VariablePlayGroundDialogFactory(); private SimpleBooleanProperty checkForUpdates; - private PostProcessingStepPanel postProcessingStepPanel; private SimpleStringProperty filterBlacklist; private SimpleStringProperty filterWhitelist; private SimpleBooleanProperty deleteOrphanedRecordingMetadata; + private SimpleIntegerProperty restrictBitrate; public SettingsTab(List sites, Recorder recorder) { this.sites = sites; @@ -213,6 +214,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { filterBlacklist = new SimpleStringProperty(null, "filterBlacklist", settings.filterBlacklist); filterWhitelist = new SimpleStringProperty(null, "filterWhitelist", settings.filterWhitelist); deleteOrphanedRecordingMetadata = new SimpleBooleanProperty(null, "deleteOrphanedRecordingMetadata", settings.deleteOrphanedRecordingMetadata); + restrictBitrate = new SimpleIntegerProperty(null, "restrictBitrate", settings.restrictBitrate); } private void createGui() { @@ -275,6 +277,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { Setting.of("Split recordings after", splitAfter).converter(SplitAfterOption.converter()).onChange(this::splitValuesChanged), Setting.of("Split recordings bigger than", splitBiggerThan).converter(SplitBiggerThanOption.converter()).onChange(this::splitValuesChanged), Setting.of("Restrict Resolution", resolutionRange, "Only record streams with resolution within the given range"), + Setting.of("Restrict Video Bitrate (kbps, 0 = unlimited)", restrictBitrate, "Only record streams with a video bitrate below this limit (kbps)"), Setting.of("Concurrent Recordings (0 = unlimited)", concurrentRecordings), Setting.of("Default Priority", defaultPriority, "lowest 0 - 10000 highest"), Setting.of("Default duration for \"Record until\" (minutes)", recordUntilDefaultDurationInMinutes), @@ -544,11 +547,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { return transition; } - public record SplitAfterOption(String label, int value) { - - public int getValue() { - return value; - } + public record SplitAfterOption(String label, @Getter int value) { @Override public String toString() { @@ -590,11 +589,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { } } - public record SplitBiggerThanOption(String label, long value) { - - public long getValue() { - return value; - } + public record SplitBiggerThanOption(String label, @Getter long value) { @Override public String toString() { 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 ca4d7c60..ec3e432a 100644 --- a/client/src/main/java/ctbrec/ui/sites/myfreecams/MyFreeCamsTableTab.java +++ b/client/src/main/java/ctbrec/ui/sites/myfreecams/MyFreeCamsTableTab.java @@ -1,37 +1,7 @@ package ctbrec.ui.sites.myfreecams; -import static java.nio.charset.StandardCharsets.*; -import static java.nio.file.StandardOpenOption.*; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - -import javax.xml.bind.JAXBException; - -import org.json.JSONArray; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.iheartradio.m3u8.ParseException; import com.iheartradio.m3u8.PlaylistException; - import ctbrec.Config; import ctbrec.GlobalThreadPool; import ctbrec.Model; @@ -46,15 +16,7 @@ import ctbrec.ui.controls.CustomMouseBehaviorContextMenu; import ctbrec.ui.controls.SearchBox; import ctbrec.ui.menu.ModelMenuContributor; import ctbrec.ui.tabs.TabSelectionListener; -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.beans.property.*; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; @@ -65,19 +27,8 @@ import javafx.geometry.Insets; import javafx.geometry.Point2D; import javafx.geometry.Pos; import javafx.scene.Cursor; -import javafx.scene.control.Button; -import javafx.scene.control.CheckMenuItem; -import javafx.scene.control.ContextMenu; -import javafx.scene.control.Label; -import javafx.scene.control.MenuItem; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.SelectionMode; -import javafx.scene.control.SeparatorMenuItem; -import javafx.scene.control.Tab; -import javafx.scene.control.TableColumn; +import javafx.scene.control.*; import javafx.scene.control.TableColumn.SortType; -import javafx.scene.control.TableView; -import javafx.scene.control.Tooltip; import javafx.scene.input.ContextMenuEvent; import javafx.scene.input.MouseEvent; import javafx.scene.layout.BorderPane; @@ -85,22 +36,41 @@ import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.stage.FileChooser; import javafx.util.Duration; +import lombok.extern.slf4j.Slf4j; +import org.json.JSONArray; +import org.json.JSONObject; +import javax.xml.bind.JAXBException; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.net.URLDecoder; +import java.nio.file.Files; +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.WRITE; + +@Slf4j public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { - private static final Logger LOG = LoggerFactory.getLogger(MyFreeCamsTableTab.class); - private ScrollPane scrollPane = new ScrollPane(); - private TableView table = new TableView<>(); - private ObservableList filteredModels = FXCollections.observableArrayList(); - private ObservableList observableModels = FXCollections.observableArrayList(); + private final ScrollPane scrollPane = new ScrollPane(); + private final TableView table = new TableView<>(); + private final ObservableList filteredModels = FXCollections.observableArrayList(); + private final ObservableList observableModels = FXCollections.observableArrayList(); + private final Recorder recorder; + private final MyFreeCams mfc; + private final ReentrantLock lock = new ReentrantLock(); + private final Label count = new Label("models"); + private final List> columns = new ArrayList<>(); private TableUpdateService updateService; - private MyFreeCams mfc; - private ReentrantLock lock = new ReentrantLock(); private SearchBox filterInput; - private Label count = new Label("models"); - private List> columns = new ArrayList<>(); private ContextMenu popup; private long lastJsonWrite = 0; - private Recorder recorder; public MyFreeCamsTableTab(MyFreeCams mfc, Recorder recorder) { this.mfc = mfc; @@ -118,7 +88,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) { @@ -139,16 +109,16 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { } } - for (Iterator iterator = observableModels.iterator(); iterator.hasNext();) { + for (Iterator iterator = observableModels.iterator(); iterator.hasNext(); ) { ModelTableRow model = iterator.next(); var found = false; for (SessionState sessionState : sessionStates) { - if(Objects.equals(sessionState.getUid(), model.uid)) { + if (Objects.equals(sessionState.getUid(), model.uid)) { found = true; break; } } - if(!found) { + if (!found) { iterator.remove(); } } @@ -161,7 +131,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { table.sort(); long now = System.currentTimeMillis(); - if( (now - lastJsonWrite) > TimeUnit.SECONDS.toMillis(30)) { + if ((now - lastJsonWrite) > TimeUnit.SECONDS.toMillis(30)) { lastJsonWrite = now; saveData(); } @@ -174,7 +144,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { filterInput = new SearchBox(false); filterInput.setPromptText("Filter"); - filterInput.textProperty().addListener( (observableValue, oldValue, newValue) -> { + filterInput.textProperty().addListener((observableValue, oldValue, newValue) -> { String filter = filterInput.getText(); Config.getInstance().getSettings().mfcModelsTableFilter = filter; lock.lock(); @@ -218,7 +188,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { popup.hide(); } }); - table.getColumns().addListener((ListChangeListener>)(e -> saveState())); + table.getColumns().addListener((ListChangeListener>) (e -> saveState())); var idx = 0; TableColumn uid = createTableColumn("UID", 65, idx++); @@ -313,9 +283,9 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { ContextMenu menu = new CustomMouseBehaviorContextMenu(); ModelMenuContributor.newContributor(getTabPane(), Config.getInstance(), recorder) // - .withStartStopCallback(m -> getTabPane().setCursor(Cursor.DEFAULT)) // - .afterwards(table::refresh) - .contributeToMenu(selectedModels, menu); + .withStartStopCallback(m -> getTabPane().setCursor(Cursor.DEFAULT)) // + .afterwards(table::refresh) + .contributeToMenu(selectedModels, menu); addDebuggingInDevMode(menu, selectedModels); @@ -331,11 +301,11 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { try { List sources = m.getStreamSources(); for (StreamSource src : sources) { - LOG.info("m:{} s:{} bandwidth:{} height:{}", m.getName(), src.mediaPlaylistUrl, src.bandwidth, src.height); + log.info("m:{} s:{} bandwidth:{} height:{}", m.getName(), src.getMediaPlaylistUrl(), src.getBandwidth(), src.getHeight()); } - LOG.info("==============="); + log.info("==============="); } catch (IOException | ExecutionException | ParseException | PlaylistException | JAXBException e1) { - LOG.error("Couldn't get stream sources", e1); + log.error("Couldn't get stream sources", e1); } } })); @@ -357,7 +327,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { } private void addTableColumnIfEnabled(TableColumn tc) { - if(isColumnEnabled(tc)) { + if (isColumnEnabled(tc)) { table.getColumns().add(tc); } } @@ -444,7 +414,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { ps.println(); } } catch (Exception e) { - LOG.debug("Couldn't write mfc models table data", e); + log.debug("Couldn't write mfc models table data", e); } } } @@ -466,7 +436,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { boolean added = false; for (int i = table.getColumns().size() - 1; i >= 0; i--) { TableColumn other = table.getColumns().get(i); - LOG.debug("Adding column {}", tc.getText()); + log.debug("Adding column {}", tc.getText()); int idx = (int) tc.getUserData(); int otherIdx = (int) other.getUserData(); if (otherIdx < idx) { @@ -516,7 +486,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { @Override public void deselected() { - if(updateService != null) { + if (updateService != null) { updateService.cancel(); } saveData(); @@ -546,26 +516,26 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { data.put(model); } var file = new File(Config.getInstance().getConfigDir(), "mfc-models.json"); - Files.write(file.toPath(), data.toString(2).getBytes(UTF_8), CREATE, WRITE); + Files.writeString(file.toPath(), data.toString(2), CREATE, WRITE); saveState(); } catch (Exception e) { - LOG.debug("Couldn't write mfc models table data: {}", e.getMessage()); + log.debug("Couldn't write mfc models table data: {}", e.getMessage()); } } private void loadData() { try { var file = new File(Config.getInstance().getConfigDir(), "mfc-models.json"); - if(!file.exists()) { + if (!file.exists()) { return; } - var json = new String(Files.readAllBytes(file.toPath()), UTF_8); + var json = Files.readString(file.toPath()); var data = new JSONArray(json); for (var i = 0; i < data.length(); i++) { createRow(data, i).ifPresent(observableModels::add); } } catch (Exception e) { - LOG.debug("Couldn't read mfc models table data: {}", e.getMessage()); + log.debug("Couldn't read mfc models table data: {}", e.getMessage()); } } @@ -634,10 +604,10 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { private void restoreColumnOrder() { String[] columnIds = Config.getInstance().getSettings().mfcModelsTableColumnIds; - ObservableList> tableColumns = table.getColumns(); + ObservableList> tableColumns = table.getColumns(); for (var i = 0; i < columnIds.length; i++) { for (var j = 0; j < table.getColumns().size(); j++) { - if(Objects.equals(columnIds[i], tableColumns.get(j).getId())) { + if (Objects.equals(columnIds[i], tableColumns.get(j).getId())) { TableColumn col = tableColumns.get(j); tableColumns.remove(j); // NOSONAR tableColumns.add(i, col); @@ -661,21 +631,21 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { private static class ModelTableRow { private Integer uid; - private StringProperty name = new SimpleStringProperty(); - private StringProperty state = new SimpleStringProperty(); - private DoubleProperty camScore = new SimpleDoubleProperty(); - private StringProperty newModel = new SimpleStringProperty(); - private StringProperty ethnic = new SimpleStringProperty(); - private StringProperty country = new SimpleStringProperty(); - private StringProperty continent = new SimpleStringProperty(); - private StringProperty occupation = new SimpleStringProperty(); - private StringProperty tags = new SimpleStringProperty(); - private StringProperty blurp = new SimpleStringProperty(); - private StringProperty topic = new SimpleStringProperty(); - private BooleanProperty isHd = new SimpleBooleanProperty(); - private BooleanProperty isWebrtc = new SimpleBooleanProperty(); - private SimpleIntegerProperty uidProperty = new SimpleIntegerProperty(); - private SimpleIntegerProperty flagsProperty = new SimpleIntegerProperty(); + private final StringProperty name = new SimpleStringProperty(); + private final StringProperty state = new SimpleStringProperty(); + private final DoubleProperty camScore = new SimpleDoubleProperty(); + private final StringProperty newModel = new SimpleStringProperty(); + private final StringProperty ethnic = new SimpleStringProperty(); + private final StringProperty country = new SimpleStringProperty(); + private final StringProperty continent = new SimpleStringProperty(); + private final StringProperty occupation = new SimpleStringProperty(); + private final StringProperty tags = new SimpleStringProperty(); + private final StringProperty blurp = new SimpleStringProperty(); + private final StringProperty topic = new SimpleStringProperty(); + private final BooleanProperty isHd = new SimpleBooleanProperty(); + private final BooleanProperty isWebrtc = new SimpleBooleanProperty(); + private final SimpleIntegerProperty uidProperty = new SimpleIntegerProperty(); + private final SimpleIntegerProperty flagsProperty = new SimpleIntegerProperty(); public ModelTableRow(SessionState st) { update(st); @@ -691,9 +661,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { setProperty(state, Optional.ofNullable(st.getVs()).map(vs -> ctbrec.sites.mfc.State.of(vs).toString())); 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" : ""); - } + isNew.ifPresent(integer -> newModel.set(integer == 1 ? "new" : "")); 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)); @@ -712,16 +680,12 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { } 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) { - LOG.warn("Couldn't url decode topic", e); - } + tpc = URLDecoder.decode(tpc, UTF_8); topic.set(tpc); } private void setProperty(Property prop, Optional value) { - if(value.isPresent() && !Objects.equals(value.get(), prop.getValue())) { + if (value.isPresent() && !Objects.equals(value.get(), prop.getValue())) { prop.setValue(value.get()); } } @@ -804,11 +768,8 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { return false; ModelTableRow other = (ModelTableRow) obj; if (uid == null) { - if (other.uid != null) - return false; - } else if (!uid.equals(other.uid)) - return false; - return true; + return other.uid == null; + } else return uid.equals(other.uid); } } -} \ No newline at end of file +} diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java index c49f80ed..da831a2f 100644 --- a/common/src/main/java/ctbrec/Settings.java +++ b/common/src/main/java/ctbrec/Settings.java @@ -219,4 +219,5 @@ public class Settings { public boolean dreamcamVR = false; public String filterBlacklist = ""; public String filterWhitelist = ""; + public int restrictBitrate = 0; } diff --git a/common/src/main/java/ctbrec/recorder/OnlineMonitor.java b/common/src/main/java/ctbrec/recorder/OnlineMonitor.java index 503802df..e153e6c8 100644 --- a/common/src/main/java/ctbrec/recorder/OnlineMonitor.java +++ b/common/src/main/java/ctbrec/recorder/OnlineMonitor.java @@ -65,6 +65,8 @@ public class OnlineMonitor extends Thread { } private void updateModels(List models) { + // sort models by adding date (new go first) + models.sort((a, b) -> b.getAddedTimestamp().compareTo(a.getAddedTimestamp())); // sort models by priority models.sort((a, b) -> b.getPriority() - a.getPriority()); // submit online check jobs to the executor for the model's site diff --git a/common/src/main/java/ctbrec/recorder/download/AbstractDownload.java b/common/src/main/java/ctbrec/recorder/download/AbstractDownload.java index 356c66f2..0e3e64e2 100644 --- a/common/src/main/java/ctbrec/recorder/download/AbstractDownload.java +++ b/common/src/main/java/ctbrec/recorder/download/AbstractDownload.java @@ -93,23 +93,27 @@ public abstract class AbstractDownload implements RecordingProcess { streamSources.forEach(ss -> log.debug(ss.toString())); StreamSource source = streamSources.get(model.getStreamUrlIndex()); log.debug("{} selected {}", model.getName(), source); - selectedResolution = source.height; + selectedResolution = source.getHeight(); return source; } else { // filter out stream resolutions, which are out of range of the configured min and max int minRes = Config.getInstance().getSettings().minimumResolution; int maxRes = Config.getInstance().getSettings().maximumResolution; + int bitrateLimit = Config.getInstance().getSettings().restrictBitrate * 1024; List filteredStreamSources = streamSources.stream() - .filter(src -> src.height == 0 || src.height == UNKNOWN || minRes <= src.height) - .filter(src -> src.height == 0 || src.height == UNKNOWN || maxRes >= src.height) + .filter(src -> src.getHeight() == 0 || src.getHeight() == UNKNOWN || minRes <= src.getHeight()) + .filter(src -> src.getHeight() == 0 || src.getHeight() == UNKNOWN || maxRes >= src.getHeight()) + .filter(src -> bitrateLimit == 0 || src.getBandwidth() == UNKNOWN || src.getBandwidth() <= bitrateLimit) .toList(); if (filteredStreamSources.isEmpty()) { + // TODO save, why a stream has been filtered out and convey this information to the UI, so that the user understands why a recording + // doesn't start throw new ExecutionException(new NoStreamFoundException("No stream left in playlist")); } else { StreamSource source = filteredStreamSources.get(filteredStreamSources.size() - 1); log.debug("{} selected {}", model.getName(), source); - selectedResolution = source.height; + selectedResolution = source.getHeight(); return source; } } diff --git a/common/src/main/java/ctbrec/recorder/download/StreamSource.java b/common/src/main/java/ctbrec/recorder/download/StreamSource.java index c9ed2211..891497da 100644 --- a/common/src/main/java/ctbrec/recorder/download/StreamSource.java +++ b/common/src/main/java/ctbrec/recorder/download/StreamSource.java @@ -1,46 +1,20 @@ package ctbrec.recorder.download; +import lombok.Getter; +import lombok.Setter; + import java.text.DecimalFormat; +@Getter +@Setter public class StreamSource implements Comparable { public static final int ORIGIN = Integer.MAX_VALUE - 1; public static final int UNKNOWN = Integer.MAX_VALUE; - public int bandwidth; - public int width; - public int height; - public String mediaPlaylistUrl; - public int getBandwidth() { - return bandwidth; - } - - public void setBandwidth(int bandwidth) { - this.bandwidth = bandwidth; - } - - public int getWidth() { - return width; - } - - public void setWidth(int width) { - this.width = width; - } - - public int getHeight() { - return height; - } - - public void setHeight(int height) { - this.height = height; - } - - public String getMediaPlaylistUrl() { - return mediaPlaylistUrl; - } - - public void setMediaPlaylistUrl(String mediaPlaylistUrl) { - this.mediaPlaylistUrl = mediaPlaylistUrl; - } + private int bandwidth; + private int width; + private int height; + private String mediaPlaylistUrl; @Override public String toString() { @@ -51,6 +25,9 @@ public class StreamSource implements Comparable { } else if (height == ORIGIN) { return "Origin"; } else { + if (height * width > 0) { + return height + "p (" + df.format(mbit) + " Mbit/s) [" + df.format((float) bandwidth / height / width) + " bit/pix]"; + } return height + "p (" + df.format(mbit) + " Mbit/s)"; } } @@ -62,7 +39,7 @@ public class StreamSource implements Comparable { @Override public int compareTo(StreamSource o) { int heightDiff = height - o.height; - if(heightDiff != 0 && height != UNKNOWN && o.height != UNKNOWN) { + if (heightDiff != 0 && height != UNKNOWN && o.height != UNKNOWN) { return heightDiff; } else { return bandwidth - o.bandwidth; @@ -98,10 +75,6 @@ public class StreamSource implements Comparable { return false; } else if (!mediaPlaylistUrl.equals(other.mediaPlaylistUrl)) return false; - if (width != other.width) - return false; - return true; + return width == other.width; } - - } 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 d2d76ec0..8c161f8d 100644 --- a/common/src/main/java/ctbrec/recorder/download/hls/AbstractHlsDownload.java +++ b/common/src/main/java/ctbrec/recorder/download/hls/AbstractHlsDownload.java @@ -29,6 +29,7 @@ import javax.xml.bind.JAXBException; import java.io.*; import java.net.MalformedURLException; import java.net.SocketTimeoutException; +import java.net.URI; import java.net.URL; import java.nio.file.Files; import java.text.DecimalFormat; @@ -248,7 +249,7 @@ public abstract class AbstractHlsDownload extends AbstractDownload { } try { LOG.debug("Waiting {}ms before trying to update the playlist URL", A_FEW_SECONDS); - waitSomeTime(A_FEW_SECONDS); + waitSomeTime(); segmentPlaylistUrl = getSegmentPlaylistUrl(model); } catch (Exception e) { LOG.error("Playlist URL couldn't be updated after waiting for {}ms", A_FEW_SECONDS, e); @@ -264,7 +265,7 @@ public abstract class AbstractHlsDownload extends AbstractDownload { } StreamSource selectedStreamSource = selectStreamSource(streamSources); String url = selectedStreamSource.getMediaPlaylistUrl(); - selectedResolution = selectedStreamSource.height; + selectedResolution = selectedStreamSource.getHeight(); LOG.debug("Segment playlist url {}", url); return url; } @@ -272,7 +273,7 @@ public abstract class AbstractHlsDownload extends AbstractDownload { protected SegmentPlaylist getNextSegments(String segmentPlaylistUrl) throws IOException, ParseException, PlaylistException { Instant start = Instant.now(); recordingEvents.add(RecordingEvent.of("Playlist request")); - URL segmentsUrl = new URL(segmentPlaylistUrl); + URL segmentsUrl = URI.create(segmentPlaylistUrl).toURL(); Builder builder = new Request.Builder().url(segmentsUrl); addHeaders(builder, Optional.ofNullable(model).map(Model::getHttpHeaderFactory).map(HttpHeaderFactory::createSegmentPlaylistHeaders).orElse(new HashMap<>()), model); Request request = builder.build(); @@ -321,27 +322,7 @@ public abstract class AbstractHlsDownload extends AbstractDownload { List tracks = mediaPlaylist.getTracks(); for (TrackData trackData : tracks) { - if (trackData.hasMapInfo()) { - var mapInfoUri = trackData.getMapInfo().getUri(); - if (!mapInfoUri.startsWith("http")) { - URL context = new URL(segmentPlaylistUrl); - mapInfoUri = new URL(context, mapInfoUri).toExternalForm(); - } - lsp.segments.add(new Segment(mapInfoUri, Math.max(0, trackData.getTrackInfo().duration))); - } - String uri = trackData.getUri(); - if (!uri.startsWith("http")) { - URL context = new URL(segmentPlaylistUrl); - uri = new URL(context, uri).toExternalForm(); - } - lsp.totalDuration += trackData.getTrackInfo().duration; - lsp.segments.add(new Segment(uri, Math.max(0, trackData.getTrackInfo().duration))); - if (trackData.hasEncryptionData()) { - lsp.encrypted = true; - EncryptionData data = trackData.getEncryptionData(); - lsp.encryptionKeyUrl = data.getUri(); - lsp.encryptionMethod = data.getMethod().getValue(); - } + parseSegmentData(lsp, trackData); } lsp.avgSegDuration = lsp.totalDuration / tracks.size(); return lsp; @@ -349,6 +330,30 @@ public abstract class AbstractHlsDownload extends AbstractDownload { throw new InvalidPlaylistException("Playlist has no media playlist"); } + private void parseSegmentData(SegmentPlaylist lsp, TrackData trackData) { + if (trackData.hasMapInfo()) { + var mapInfoUri = trackData.getMapInfo().getUri(); + if (!mapInfoUri.startsWith("http")) { + URI context = URI.create(segmentPlaylistUrl); + mapInfoUri = context.resolve(mapInfoUri).toString(); + } + lsp.segments.add(new Segment(mapInfoUri, Math.max(0, trackData.getTrackInfo().duration))); + } + String uri = trackData.getUri(); + if (!uri.startsWith("http")) { + URI context = URI.create(segmentPlaylistUrl); + uri = context.resolve(uri).toString(); + } + lsp.totalDuration += trackData.getTrackInfo().duration; + lsp.segments.add(new Segment(uri, Math.max(0, trackData.getTrackInfo().duration))); + if (trackData.hasEncryptionData()) { + lsp.encrypted = true; + EncryptionData data = trackData.getEncryptionData(); + lsp.encryptionKeyUrl = data.getUri(); + lsp.encryptionMethod = data.getMethod().getValue(); + } + } + protected void emptyPlaylistCheck(SegmentPlaylist playlist) { if (playlist.segments.isEmpty()) { playlistEmptyCount++; @@ -423,9 +428,9 @@ public abstract class AbstractHlsDownload extends AbstractDownload { * This is used to slow down retries, if something is wrong with the playlist. * E.g. HTTP 403 or 404 */ - protected void waitSomeTime(long waitForMillis) { + protected void waitSomeTime() { try { - Thread.sleep(waitForMillis); + Thread.sleep(A_FEW_SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); if (running) { @@ -452,11 +457,6 @@ public abstract class AbstractHlsDownload extends AbstractDownload { return running; } - @Override - public int getSelectedResolution() { - return selectedResolution; - } - private static class RecordingEvent { Instant timestamp; String message; diff --git a/common/src/main/java/ctbrec/recorder/download/hls/FfmpegHlsDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/FfmpegHlsDownload.java index 8fa94df4..9db47cfe 100644 --- a/common/src/main/java/ctbrec/recorder/download/hls/FfmpegHlsDownload.java +++ b/common/src/main/java/ctbrec/recorder/download/hls/FfmpegHlsDownload.java @@ -28,7 +28,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.SocketTimeoutException; -import java.net.URL; +import java.net.URI; import java.nio.file.Files; import java.time.Duration; import java.time.Instant; @@ -61,7 +61,6 @@ public class FfmpegHlsDownload extends AbstractDownload { private volatile boolean running; private volatile boolean started; - private int selectedResolution = 0; public FfmpegHlsDownload(HttpClient httpClient) { this.httpClient = httpClient; @@ -81,11 +80,6 @@ public class FfmpegHlsDownload extends AbstractDownload { throw new ProcessExitedUncleanException("Couldn't spawn FFmpeg"); } } - - @Override - public int getSelectedResolution() { - return selectedResolution; - } @Override public void stop() { @@ -157,7 +151,7 @@ public class FfmpegHlsDownload extends AbstractDownload { public boolean isRunning() { return running; } - + @Override public void postProcess(Recording recording) { // nothing to do @@ -206,7 +200,7 @@ public class FfmpegHlsDownload extends AbstractDownload { } catch (Exception e) { LOG.error("Error while downloading MP4", e); stop(); - } + } if (!model.isOnline()) { LOG.debug("Model {} not online. Stop recording.", model); stop(); @@ -232,8 +226,8 @@ public class FfmpegHlsDownload extends AbstractDownload { } StreamSource selectedStreamSource = selectStreamSource(streamSources); String playlistUrl = selectedStreamSource.getMediaPlaylistUrl(); - selectedResolution = selectedStreamSource.height; - + selectedResolution = selectedStreamSource.getHeight(); + Request req = new Request.Builder() .url(playlistUrl) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) @@ -255,8 +249,8 @@ public class FfmpegHlsDownload extends AbstractDownload { } String uri = firstTrack.getUri(); if (!uri.startsWith("http")) { - URL context = new URL(playlistUrl); - uri = new URL(context, uri).toExternalForm(); + URI context = URI.create(playlistUrl); + uri = context.resolve(uri).toURL().toExternalForm(); } LOG.debug("Media url {}", uri); return uri; @@ -301,16 +295,16 @@ public class FfmpegHlsDownload extends AbstractDownload { } } } catch (SocketTimeoutException e) { - LOG.debug("Socket timeout while downloading MP4 for {}. Stop recording.", model.getName()); - model.delay(); - stop(); + LOG.debug("Socket timeout while downloading MP4 for {}. Stop recording.", model.getName()); + model.delay(); + stop(); } catch (IOException e) { - LOG.debug("IO error while downloading MP4 for {}. Stop recording.", model.getName()); - model.delay(); - stop(); + LOG.debug("IO error while downloading MP4 for {}. Stop recording.", model.getName()); + model.delay(); + stop(); } catch (Exception e) { - LOG.error("Error while downloading MP4", e); - stop(); + LOG.error("Error while downloading MP4", e); + stop(); } finally { ffmpegStreamLock.unlock(); } @@ -318,7 +312,7 @@ public class FfmpegHlsDownload extends AbstractDownload { running = false; }); } - + protected void createTargetDirectory() throws IOException { Files.createDirectories(targetFile.getParentFile().toPath()); } diff --git a/common/src/main/java/ctbrec/recorder/download/hls/HlsdlDownload.java b/common/src/main/java/ctbrec/recorder/download/hls/HlsdlDownload.java index 34f4cd87..05e7e39b 100644 --- a/common/src/main/java/ctbrec/recorder/download/hls/HlsdlDownload.java +++ b/common/src/main/java/ctbrec/recorder/download/hls/HlsdlDownload.java @@ -25,7 +25,6 @@ import java.util.Map.Entry; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.regex.Pattern; -import java.util.stream.Collectors; import static ctbrec.recorder.download.StreamSource.UNKNOWN; import static java.util.concurrent.TimeUnit.SECONDS; @@ -141,9 +140,9 @@ public class HlsdlDownload extends AbstractDownload { int minRes = Config.getInstance().getSettings().minimumResolution; int maxRes = Config.getInstance().getSettings().maximumResolution; List filteredStreamSources = streamSources.stream() - .filter(src -> src.height == 0 || src.height == UNKNOWN || minRes <= src.height) - .filter(src -> src.height == 0 || src.height == UNKNOWN || maxRes >= src.height) - .collect(Collectors.toList()); + .filter(src -> src.getHeight() == 0 || src.getHeight() == UNKNOWN || minRes <= src.getHeight()) + .filter(src -> src.getHeight() == 0 || src.getHeight() == UNKNOWN || maxRes >= src.getHeight()) + .toList(); if (filteredStreamSources.isEmpty()) { throw new ExecutionException(new NoStreamFoundException("No stream left in playlist")); diff --git a/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvDownload.java b/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvDownload.java index 6ad69c56..0e8819a2 100644 --- a/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvDownload.java +++ b/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvDownload.java @@ -134,9 +134,9 @@ public class AmateurTvDownload extends AbstractDownload { running = true; try { StreamSource src = model.getStreamSources().get(0); - LOG.debug("Loading video from {}", src.mediaPlaylistUrl); + LOG.debug("Loading video from {}", src.getMediaPlaylistUrl()); Request request = new Request.Builder() - .url(src.mediaPlaylistUrl) + .url(src.getMediaPlaylistUrl()) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .header(ACCEPT, "*/*") .header(ACCEPT_LANGUAGE, "en") diff --git a/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvModel.java b/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvModel.java index 4cd452f0..613e9fdd 100644 --- a/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvModel.java +++ b/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvModel.java @@ -76,10 +76,10 @@ public class AmateurTvModel extends AbstractModel { String value = (String) item; String[] res = value.split("x"); StreamSource src = new StreamSource(); - src.mediaPlaylistUrl = MessageFormat.format("{0}&variant={1}", mediaPlaylistUrl, res[1]); - src.width = Integer.parseInt(res[0]); - src.height = Integer.parseInt(res[1]); - src.bandwidth = 0; + src.setMediaPlaylistUrl(MessageFormat.format("{0}&variant={1}", mediaPlaylistUrl, res[1])); + src.setWidth(Integer.parseInt(res[0])); + src.setHeight(Integer.parseInt(res[1])); + src.setBandwidth(0); streamSources.add(src); }); return streamSources; diff --git a/common/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java b/common/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java index d618b5d3..c2b205b3 100644 --- a/common/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java +++ b/common/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java @@ -10,14 +10,14 @@ import ctbrec.Config; import ctbrec.io.HtmlParser; import ctbrec.io.HttpException; import ctbrec.recorder.download.StreamSource; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import okhttp3.FormBody; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import org.json.JSONObject; import org.jsoup.nodes.Element; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; @@ -30,15 +30,16 @@ import static ctbrec.ErrorMessages.HTTP_RESPONSE_BODY_IS_NULL; import static ctbrec.Model.State.*; import static ctbrec.io.HttpConstants.*; +@Slf4j public class BongaCamsModel extends AbstractModel { private static final String ARGS = "args[]"; - private static final Logger LOG = LoggerFactory.getLogger(BongaCamsModel.class); private static final String SUCCESS = "success"; private static final String STATUS = "status"; private static final Pattern ONLINE_BADGE_REGEX = Pattern.compile("class=\"badge_online\s*\""); + @Setter private boolean online = false; private final transient List streamSources = new ArrayList<>(); private int[] resolution; @@ -102,23 +103,21 @@ public class BongaCamsModel extends AbstractModel { } } } catch (Exception e) { - LOG.warn("Couldn't check if model is connected: {}", e.getLocalizedMessage()); + log.warn("Couldn't check if model is connected: {}", e.getLocalizedMessage()); return false; } } public State mapState(String roomState) { - switch (roomState) { - case "private", "fullprivate": - return PRIVATE; - case "group": - return GROUP; - case "public": - return ONLINE; - default: - LOG.debug(roomState); - return OFFLINE; - } + return switch (roomState) { + case "private", "fullprivate" -> PRIVATE; + case "group" -> GROUP; + case "public" -> ONLINE; + default -> { + log.debug(roomState); + yield OFFLINE; + } + }; } private boolean isStreamAvailable() { @@ -134,7 +133,7 @@ public class BongaCamsModel extends AbstractModel { } } } catch (Exception e) { - LOG.warn("Couldn't check if stream is available: {}", e.getLocalizedMessage()); + log.warn("Couldn't check if stream is available: {}", e.getLocalizedMessage()); return false; } } @@ -162,10 +161,6 @@ public class BongaCamsModel extends AbstractModel { } } - public void setOnline(boolean online) { - this.online = online; - } - @Override public State getOnlineState(boolean failFast) throws IOException, ExecutionException { if (!failFast) { @@ -181,11 +176,6 @@ public class BongaCamsModel extends AbstractModel { return onlineState; } - @Override - public void setOnlineState(State onlineState) { - this.onlineState = onlineState; - } - @Override public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException { String streamUrl = getStreamUrl(); @@ -208,16 +198,16 @@ public class BongaCamsModel extends AbstractModel { streamSources.clear(); for (PlaylistData playlistData : master.getPlaylists()) { StreamSource streamsource = new StreamSource(); - streamsource.mediaPlaylistUrl = streamUrl.replace("playlist.m3u8", playlistData.getUri()); + streamsource.setMediaPlaylistUrl(streamUrl.replace("playlist.m3u8", playlistData.getUri())); if (playlistData.hasStreamInfo()) { StreamInfo info = playlistData.getStreamInfo(); - streamsource.bandwidth = info.getBandwidth(); - streamsource.width = info.hasResolution() ? info.getResolution().width : 0; - streamsource.height = info.hasResolution() ? info.getResolution().height : 0; + streamsource.setBandwidth(info.getBandwidth()); + streamsource.setWidth(info.hasResolution() ? info.getResolution().width : 0); + streamsource.setHeight(info.hasResolution() ? info.getResolution().height : 0); } else { - streamsource.bandwidth = 0; - streamsource.width = 0; - streamsource.height = 0; + streamsource.setBandwidth(0); + streamsource.setWidth(0); + streamsource.setHeight(0); } streamSources.add(streamsource); } @@ -261,7 +251,7 @@ public class BongaCamsModel extends AbstractModel { if (response.isSuccessful()) { JSONObject json = new JSONObject(Objects.requireNonNull(response.body(), HTTP_RESPONSE_BODY_IS_NULL).string()); if (!json.optString(STATUS).equals(SUCCESS)) { - LOG.error("Sending tip failed {}", json.toString(2)); + log.error("Sending tip failed {}", json.toString(2)); throw new IOException("Sending tip failed"); } } else { @@ -283,13 +273,13 @@ public class BongaCamsModel extends AbstractModel { List sources = getStreamSources(); Collections.sort(sources); StreamSource best = sources.get(sources.size() - 1); - resolution = new int[]{best.width, best.height}; + resolution = new int[]{best.getWidth(), best.getHeight()}; } catch (InterruptedException e) { Thread.currentThread().interrupt(); - LOG.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); + log.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); resolution = new int[2]; } catch (ExecutionException | IOException | ParseException | PlaylistException e) { - LOG.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); + log.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); resolution = new int[2]; } } @@ -303,7 +293,7 @@ public class BongaCamsModel extends AbstractModel { } String url = getSite().getBaseUrl() + "/follow/" + getName(); - LOG.debug("Calling {}", url); + log.debug("Calling {}", url); RequestBody body = new FormBody.Builder() .add("src", "public-chat") .add("_csrf_token", getCsrfToken()) @@ -319,10 +309,10 @@ public class BongaCamsModel extends AbstractModel { String msg = Objects.requireNonNull(resp.body(), HTTP_RESPONSE_BODY_IS_NULL).string(); JSONObject json = new JSONObject(msg); if (json.optBoolean(SUCCESS)) { - LOG.debug("Follow/Unfollow -> {}", msg); + log.debug("Follow/Unfollow -> {}", msg); return true; } else { - LOG.debug(msg); + log.debug(msg); throw new IOException("Response was " + msg); } } else { @@ -338,7 +328,7 @@ public class BongaCamsModel extends AbstractModel { String content = Objects.requireNonNull(resp.body(), HTTP_RESPONSE_BODY_IS_NULL).string(); Element html = HtmlParser.getTag(content, "html"); String csrfToken = html.attr("data-csrf_value"); - LOG.debug("CSRF-Token {}", csrfToken); + log.debug("CSRF-Token {}", csrfToken); return csrfToken; } else { throw new HttpException(resp.code(), resp.message()); @@ -353,7 +343,7 @@ public class BongaCamsModel extends AbstractModel { } String url = getSite().getBaseUrl() + "/unfollow/" + getName(); - LOG.debug("Calling {}", url); + log.debug("Calling {}", url); RequestBody body = new FormBody.Builder() .add("_csrf_token", getCsrfToken()) .build(); @@ -368,10 +358,10 @@ public class BongaCamsModel extends AbstractModel { String msg = Objects.requireNonNull(resp.body(), HTTP_RESPONSE_BODY_IS_NULL).string(); JSONObject json = new JSONObject(msg); if (json.optBoolean(SUCCESS)) { - LOG.debug("Follow/Unfollow -> {}", msg); + log.debug("Follow/Unfollow -> {}", msg); return true; } else { - LOG.debug(msg); + log.debug(msg); throw new IOException("Response was " + msg); } } else { @@ -388,7 +378,7 @@ public class BongaCamsModel extends AbstractModel { setOnline(true); } default -> { - LOG.debug(roomState); + log.debug(roomState); setOnlineState(OFFLINE); } } diff --git a/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java b/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java index e0fa77e8..c4544fc1 100644 --- a/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java +++ b/common/src/main/java/ctbrec/sites/cam4/Cam4Model.java @@ -13,11 +13,11 @@ import ctbrec.io.HttpException; import ctbrec.recorder.download.HttpHeaderFactory; import ctbrec.recorder.download.HttpHeaderFactoryImpl; import ctbrec.recorder.download.StreamSource; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import okhttp3.Request; import okhttp3.Response; import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; @@ -33,12 +33,13 @@ import static ctbrec.io.HttpConstants.*; import static java.util.regex.Pattern.DOTALL; import static java.util.regex.Pattern.MULTILINE; +@Slf4j public class Cam4Model extends AbstractModel { - private static final Logger LOG = LoggerFactory.getLogger(Cam4Model.class); + @Setter private String playlistUrl; private int[] resolution = null; - private JSONObject modelInfo; + private transient JSONObject modelInfo; @Override public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { @@ -57,7 +58,7 @@ public class Cam4Model extends AbstractModel { private JSONObject loadModelInfo() throws IOException { JSONObject roomState = new Cam4WsClient(Config.getInstance(), (Cam4) getSite(), this).getRoomState(); - LOG.trace(roomState.toString(2)); + log.trace(roomState.toString(2)); String state = roomState.optString("newShowsState"); setOnlineStateByShowType(state); setDescription(roomState.optString("status")); @@ -73,7 +74,7 @@ public class Cam4Model extends AbstractModel { case "PAUSED" -> onlineState = AWAY; case "OFFLINE" -> onlineState = OFFLINE; default -> { - LOG.debug("############################## Unknown show type [{} {}]", this, showType); + log.debug("############################## Unknown show type [{} {}]", this, showType); onlineState = UNKNOWN; } } @@ -86,7 +87,7 @@ public class Cam4Model extends AbstractModel { try { modelInfo = loadModelInfo(); } catch (Exception e) { - LOG.warn("Couldn't load model details {}", e.getMessage()); + log.warn("Couldn't load model details {}", e.getMessage()); } } return onlineState; @@ -99,11 +100,11 @@ public class Cam4Model extends AbstractModel { return playlistUrl; } } catch (IOException e) { - LOG.debug("Couldn't get playlist url from stream info: {}", e.getMessage()); + log.debug("Couldn't get playlist url from stream info: {}", e.getMessage()); } if (modelInfo != null && modelInfo.has("hls")) { String hls = modelInfo.optString("hls"); - LOG.debug("Stream hls: {}", hls); + log.debug("Stream hls: {}", hls); if (StringUtil.isNotBlank(hls) && hls.startsWith("http")) { playlistUrl = hls; return playlistUrl; @@ -111,7 +112,7 @@ public class Cam4Model extends AbstractModel { } if (modelInfo != null && modelInfo.has("streamUUID")) { String uuid = modelInfo.optString("streamUUID"); - LOG.debug("Stream UUID: {}", uuid); + log.debug("Stream UUID: {}", uuid); String[] parts = uuid.split("-"); if (parts.length > 3) { String urlTemplate = "https://cam4-hls.xcdnpro.com/{0}/cam4-origin-live/{1}_aac/playlist.m3u8"; @@ -133,7 +134,7 @@ public class Cam4Model extends AbstractModel { private void getPlaylistUrlFromStreamUrl() throws IOException { String url = getSite().getBaseUrl() + "/rest/v1.0/profile/" + getName() + "/streamInfo"; - LOG.trace("Getting playlist url from {}", url); + log.trace("Getting playlist url from {}", url); Request req = new Request.Builder() // @formatter:off .url(url) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) @@ -146,7 +147,7 @@ public class Cam4Model extends AbstractModel { try (Response response = site.getHttpClient().execute(req)) { if (response.isSuccessful()) { JSONObject json = new JSONObject(bodyToJsonObject(response)); - if (LOG.isTraceEnabled()) LOG.trace(json.toString(2)); + if (log.isTraceEnabled()) log.trace(json.toString(2)); if (json.has("canUseCDN")) { if (json.getBoolean("canUseCDN")) { playlistUrl = json.optString("cdnURL"); @@ -186,15 +187,15 @@ public class Cam4Model extends AbstractModel { for (PlaylistData playlist : masterPlaylist.getPlaylists()) { if (playlist.hasStreamInfo()) { StreamSource src = new StreamSource(); - src.bandwidth = playlist.getStreamInfo().getBandwidth(); - src.height = Optional.ofNullable(playlist.getStreamInfo()).map(StreamInfo::getResolution).map(res -> res.height).orElse(0); + src.setBandwidth(playlist.getStreamInfo().getBandwidth()); + src.setHeight(Optional.ofNullable(playlist.getStreamInfo()).map(StreamInfo::getResolution).map(res -> res.height).orElse(0)); if (playlist.getUri().startsWith("http")) { - src.mediaPlaylistUrl = playlist.getUri(); + src.setMediaPlaylistUrl(playlist.getUri()); } else { String baseUrl = playlistUrl.substring(0, playlistUrl.lastIndexOf('/') + 1); - src.mediaPlaylistUrl = baseUrl + playlist.getUri(); + src.setMediaPlaylistUrl(baseUrl + playlist.getUri()); } - LOG.trace("Media playlist {}", src.mediaPlaylistUrl); + log.trace("Media playlist {}", src.getMediaPlaylistUrl()); sources.add(src); } } @@ -204,7 +205,7 @@ public class Cam4Model extends AbstractModel { private MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException { String masterPlaylistUrl = getPlaylistUrl(); masterPlaylistUrl = masterPlaylistUrl.replace("_sfm4s", ""); - LOG.debug("Loading master playlist [{}]", masterPlaylistUrl); + log.debug("Loading master playlist [{}]", masterPlaylistUrl); Request.Builder builder = new Request.Builder().url(masterPlaylistUrl); getHttpHeaderFactory().createMasterPlaylistHeaders().forEach(builder::header); Request req = builder.build(); @@ -245,13 +246,13 @@ public class Cam4Model extends AbstractModel { List sources = getStreamSources(); Collections.sort(sources); StreamSource best = sources.get(sources.size() - 1); - resolution = new int[]{best.width, best.height}; + resolution = new int[]{best.getWidth(), best.getHeight()}; } catch (InterruptedException e) { Thread.currentThread().interrupt(); - LOG.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); + log.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); resolution = new int[2]; } catch (ExecutionException | IOException | ParseException | PlaylistException e) { - LOG.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); + log.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); resolution = new int[2]; } } @@ -285,10 +286,6 @@ public class Cam4Model extends AbstractModel { } } - public void setPlaylistUrl(String playlistUrl) { - this.playlistUrl = playlistUrl; - } - @Override public void setUrl(String url) { String normalizedUrl = url.toLowerCase(); diff --git a/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java b/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java index e5f6021a..03095c37 100644 --- a/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java +++ b/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java @@ -9,14 +9,15 @@ import ctbrec.AbstractModel; import ctbrec.Config; import ctbrec.io.HttpException; import ctbrec.recorder.download.StreamSource; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import okhttp3.FormBody; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import org.json.JSONException; import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; @@ -26,16 +27,19 @@ import java.util.concurrent.ExecutionException; import static ctbrec.Model.State.*; import static ctbrec.io.HttpConstants.*; +@Slf4j public class CamsodaModel extends AbstractModel { private static final String STREAM_NAME = "stream_name"; private static final String EDGE_SERVERS = "edge_servers"; private static final String STATUS = "status"; - private static final Logger LOG = LoggerFactory.getLogger(CamsodaModel.class); private transient List streamSources = null; private transient boolean isNew; + @Getter + @Setter private transient String gender; - + @Getter + @Setter private float sortOrder = 0; private final Random random = new Random(); int[] resolution = new int[2]; @@ -68,7 +72,7 @@ public class CamsodaModel extends AbstractModel { if (!isPublic(streamName)) { url.append("?token=").append(token); } - LOG.trace("Stream URL: {}", url); + log.trace("Stream URL: {}", url); return url.toString(); } @@ -104,7 +108,7 @@ public class CamsodaModel extends AbstractModel { if (playlistUrl == null) { return Collections.emptyList(); } - LOG.trace("Loading playlist {}", playlistUrl); + log.trace("Loading playlist {}", playlistUrl); Request req = new Request.Builder() .url(playlistUrl) .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) @@ -121,21 +125,21 @@ public class CamsodaModel extends AbstractModel { StreamSource streamsource = new StreamSource(); int cutOffAt = Math.max(playlistUrl.indexOf("index.m3u8"), playlistUrl.indexOf("playlist.m3u8")); String segmentPlaylistUrl = playlistUrl.substring(0, cutOffAt) + playlistData.getUri(); - streamsource.mediaPlaylistUrl = segmentPlaylistUrl; + streamsource.setMediaPlaylistUrl(segmentPlaylistUrl); if (playlistData.hasStreamInfo()) { StreamInfo info = playlistData.getStreamInfo(); - streamsource.bandwidth = info.getBandwidth(); - streamsource.width = info.hasResolution() ? info.getResolution().width : 0; - streamsource.height = info.hasResolution() ? info.getResolution().height : 0; + streamsource.setBandwidth(info.getBandwidth()); + streamsource.setWidth(info.hasResolution() ? info.getResolution().width : 0); + streamsource.setHeight(info.hasResolution() ? info.getResolution().height : 0); } else { - streamsource.bandwidth = 0; - streamsource.width = 0; - streamsource.height = 0; + streamsource.setBandwidth(0); + streamsource.setWidth(0); + streamsource.setHeight(0); } streamSources.add(streamsource); } } else { - LOG.trace("Response: {}", response.body().string()); + log.trace("Response: {}", response.body().string()); throw new HttpException(playlistUrl, response.code(), response.message()); } } @@ -177,7 +181,7 @@ public class CamsodaModel extends AbstractModel { case "private" -> onlineState = PRIVATE; case "limited" -> onlineState = GROUP; default -> { - LOG.debug("Unknown show type {}", status); + log.debug("Unknown show type {}", status); onlineState = UNKNOWN; } } @@ -211,12 +215,12 @@ public class CamsodaModel extends AbstractModel { } else { try { List sources = getStreamSources(); - LOG.debug("{}:{} stream sources {}", getSite().getName(), getName(), sources); + log.debug("{}:{} stream sources {}", getSite().getName(), getName(), sources); if (sources.isEmpty()) { return new int[]{0, 0}; } else { StreamSource src = sources.get(sources.size() - 1); - resolution = new int[]{src.width, src.height}; + resolution = new int[]{src.getWidth(), src.getHeight()}; return resolution; } } catch (IOException | ParseException | PlaylistException e) { @@ -230,7 +234,7 @@ public class CamsodaModel extends AbstractModel { String csrfToken = ((CamsodaHttpClient) site.getHttpClient()).getCsrfToken(); String url = site.getBaseUrl() + "/api/v1/tip/" + getName(); if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) { - LOG.debug("Sending tip {}", url); + log.debug("Sending tip {}", url); RequestBody body = new FormBody.Builder() .add("amount", Integer.toString(tokens.intValue())) .add("comment", "") @@ -255,7 +259,7 @@ public class CamsodaModel extends AbstractModel { @Override public boolean follow() throws IOException { String url = Camsoda.BASE_URI + "/api/v1/follow/" + getName(); - LOG.debug("Sending follow request {}", url); + log.debug("Sending follow request {}", url); String csrfToken = ((CamsodaHttpClient) site.getHttpClient()).getCsrfToken(); Request request = new Request.Builder() .url(url) @@ -278,7 +282,7 @@ public class CamsodaModel extends AbstractModel { @Override public boolean unfollow() throws IOException { String url = Camsoda.BASE_URI + "/api/v1/unfollow/" + getName(); - LOG.debug("Sending unfollow request {}", url); + log.debug("Sending unfollow request {}", url); String csrfToken = ((CamsodaHttpClient) site.getHttpClient()).getCsrfToken(); Request request = new Request.Builder() .url(url) @@ -298,14 +302,6 @@ public class CamsodaModel extends AbstractModel { } } - public float getSortOrder() { - return sortOrder; - } - - public void setSortOrder(float sortOrder) { - this.sortOrder = sortOrder; - } - public boolean isNew() { return isNew; } @@ -313,12 +309,4 @@ public class CamsodaModel extends AbstractModel { public void setNew(boolean isNew) { this.isNew = isNew; } - - public String getGender() { - return gender; - } - - public void setGender(String gender) { - this.gender = gender; - } } diff --git a/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateModel.java b/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateModel.java index d3c6fae5..a1023db5 100644 --- a/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateModel.java +++ b/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateModel.java @@ -11,13 +11,12 @@ import ctbrec.StringUtil; import ctbrec.io.HttpException; import ctbrec.io.json.ObjectMapperFactory; import ctbrec.recorder.download.StreamSource; +import lombok.extern.slf4j.Slf4j; import okhttp3.FormBody; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.EOFException; @@ -32,10 +31,10 @@ import static ctbrec.Model.State.*; import static ctbrec.io.HttpConstants.*; import static java.nio.charset.StandardCharsets.UTF_8; +@Slf4j public class ChaturbateModel extends AbstractModel { private static final String PUBLIC = "public"; - private static final Logger LOG = LoggerFactory.getLogger(ChaturbateModel.class); private int[] resolution = new int[2]; private transient StreamInfo streamInfo; private transient Instant lastStreamInfoRequest = Instant.EPOCH; @@ -60,11 +59,11 @@ public class ChaturbateModel extends AbstractModel { if (isOffline()) { roomStatus = "offline"; onlineState = State.OFFLINE; - LOG.trace("Model {} offline", getName()); + log.trace("Model {} offline", getName()); } else { StreamInfo info = getStreamInfo(); roomStatus = Optional.ofNullable(info).map(i -> i.room_status).orElse(""); - LOG.trace("Model {} room status: {}", getName(), Optional.ofNullable(info).map(i -> i.room_status).orElse("unknown")); + log.trace("Model {} room status: {}", getName(), Optional.ofNullable(info).map(i -> i.room_status).orElse("unknown")); } } else { StreamInfo info = getStreamInfo(true); @@ -165,7 +164,7 @@ public class ChaturbateModel extends AbstractModel { case "away" -> onlineState = AWAY; case "group" -> onlineState = State.GROUP; default -> { - LOG.debug("Unknown show type {}", roomStatus); + log.debug("Unknown show type {}", roomStatus); onlineState = State.UNKNOWN; } } @@ -203,16 +202,16 @@ public class ChaturbateModel extends AbstractModel { for (PlaylistData playlist : masterPlaylist.getPlaylists()) { if (playlist.hasStreamInfo()) { StreamSource src = new StreamSource(); - src.bandwidth = playlist.getStreamInfo().getBandwidth(); - src.height = playlist.getStreamInfo().getResolution().height; + src.setBandwidth(playlist.getStreamInfo().getBandwidth()); + src.setHeight(playlist.getStreamInfo().getResolution().height); String masterUrl = streamInfo.url; String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1); String segmentUri = baseUrl + playlist.getUri(); - src.mediaPlaylistUrl = segmentUri; - if (src.mediaPlaylistUrl.contains("?")) { - src.mediaPlaylistUrl = src.mediaPlaylistUrl.substring(0, src.mediaPlaylistUrl.lastIndexOf('?')); + src.setMediaPlaylistUrl(segmentUri); + if (src.getMediaPlaylistUrl().contains("?")) { + src.setMediaPlaylistUrl(src.getMediaPlaylistUrl().substring(0, src.getMediaPlaylistUrl().lastIndexOf('?'))); } - LOG.trace("Media playlist {}", src.mediaPlaylistUrl); + log.trace("Media playlist {}", src.getMediaPlaylistUrl()); sources.add(src); } } @@ -261,10 +260,10 @@ public class ChaturbateModel extends AbstractModel { String responseBody = resp2.body().string(); JSONObject json = new JSONObject(responseBody); if (!json.has("following")) { - LOG.debug(responseBody); + log.debug(responseBody); throw new IOException("Response was " + responseBody.substring(0, Math.min(responseBody.length(), 500))); } else { - LOG.debug("Follow/Unfollow -> {}", responseBody); + log.debug("Follow/Unfollow -> {}", responseBody); return json.getBoolean("following") == follow; } } else { @@ -303,7 +302,7 @@ public class ChaturbateModel extends AbstractModel { lastStreamInfoRequest = Instant.now(); if (response.isSuccessful()) { String content = response.body().string(); - LOG.trace("Raw stream info for model {}: {}", getName(), content); + log.trace("Raw stream info for model {}: {}", getName(), content); streamInfo = mapper.readValue(content, StreamInfo.class); return streamInfo; } else { @@ -355,7 +354,7 @@ public class ChaturbateModel extends AbstractModel { } private MasterPlaylist getMasterPlaylist(StreamInfo streamInfo) throws IOException, ParseException, PlaylistException { - LOG.trace("Loading master playlist {}", streamInfo.url); + log.trace("Loading master playlist {}", streamInfo.url); Request req = new Request.Builder() .url(streamInfo.url) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) @@ -363,7 +362,7 @@ public class ChaturbateModel extends AbstractModel { try (Response response = getSite().getHttpClient().execute(req)) { if (response.isSuccessful()) { String body = response.body().string(); - LOG.trace(body); + log.trace(body); InputStream inputStream = new ByteArrayInputStream(body.getBytes(UTF_8)); PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT); Playlist playlist = parser.parse(); diff --git a/common/src/main/java/ctbrec/sites/cherrytv/CherryTvModel.java b/common/src/main/java/ctbrec/sites/cherrytv/CherryTvModel.java index 63a0bf4d..b5d70cac 100644 --- a/common/src/main/java/ctbrec/sites/cherrytv/CherryTvModel.java +++ b/common/src/main/java/ctbrec/sites/cherrytv/CherryTvModel.java @@ -7,14 +7,15 @@ import com.iheartradio.m3u8.data.PlaylistData; import ctbrec.*; import ctbrec.io.HttpException; import ctbrec.recorder.download.StreamSource; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import org.json.JSONException; import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -30,14 +31,17 @@ import static ctbrec.Model.State.ONLINE; import static ctbrec.io.HttpConstants.*; import static java.nio.charset.StandardCharsets.UTF_8; +@Slf4j public class CherryTvModel extends AbstractModel { - private static final Logger LOG = LoggerFactory.getLogger(CherryTvModel.class); private static final Pattern NEXT_DATA = Pattern.compile(""); + @Setter private boolean online = false; private int[] resolution; private String masterPlaylistUrl; + @Getter + @Setter private String id; @Override @@ -57,25 +61,25 @@ public class CherryTvModel extends AbstractModel { JSONObject json = new JSONObject(m.group(1)); updateModelProperties(json); } else { - LOG.error("NEXT_DATA not found in model page {}", getUrl()); + log.error("NEXT_DATA not found in model page {}", getUrl()); return false; } } catch (JSONException e) { - LOG.error("Unable to determine online state for {}. Probably the JSON structure in NEXT_DATA changed", getName()); + log.error("Unable to determine online state for {}. Probably the JSON structure in NEXT_DATA changed", getName()); } } return online; } private void updateModelProperties(JSONObject json) { - LOG.trace(json.toString(2)); + log.trace(json.toString(2)); JSONObject apolloState = json.getJSONObject("props").getJSONObject("pageProps").getJSONObject("apolloState"); online = false; onlineState = OFFLINE; for (Iterator iter = apolloState.keys(); iter.hasNext(); ) { String key = iter.next(); if (key.startsWith("Broadcast:")) { - LOG.trace("Model properties:\n{}", apolloState.toString(2)); + log.trace("Model properties:\n{}", apolloState.toString(2)); JSONObject broadcast = apolloState.getJSONObject(key); setDisplayName(broadcast.optString("title")); online = broadcast.optString("showStatus").equalsIgnoreCase("Public") @@ -89,10 +93,6 @@ public class CherryTvModel extends AbstractModel { } } - public void setOnline(boolean online) { - this.online = online; - } - @Override public State getOnlineState(boolean failFast) throws IOException, ExecutionException { if (!failFast) { @@ -117,16 +117,16 @@ public class CherryTvModel extends AbstractModel { for (PlaylistData playlist : masterPlaylist.getPlaylists()) { if (playlist.hasStreamInfo()) { StreamSource src = new StreamSource(); - src.bandwidth = playlist.getStreamInfo().getBandwidth(); - src.height = playlist.getStreamInfo().getResolution().height; + src.setBandwidth(playlist.getStreamInfo().getBandwidth()); + src.setHeight(playlist.getStreamInfo().getResolution().height); String masterUrl = masterPlaylistUrl; String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1); String segmentUri = baseUrl + playlist.getUri(); - src.mediaPlaylistUrl = segmentUri; - if (src.mediaPlaylistUrl.contains("?")) { - src.mediaPlaylistUrl = src.mediaPlaylistUrl.substring(0, src.mediaPlaylistUrl.lastIndexOf('?')); + src.setMediaPlaylistUrl(segmentUri); + if (src.getMediaPlaylistUrl().contains("?")) { + src.setMediaPlaylistUrl(src.getMediaPlaylistUrl().substring(0, src.getMediaPlaylistUrl().lastIndexOf('?'))); } - LOG.trace("Media playlist {}", src.mediaPlaylistUrl); + log.trace("Media playlist {}", src.getMediaPlaylistUrl()); sources.add(src); } } @@ -139,10 +139,10 @@ public class CherryTvModel extends AbstractModel { private MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException { if (masterPlaylistUrl == null) { - LOG.info("Master playlist not found for {}:{}. This probably is webrtc stream", getSite().getName(), getName()); + log.info("Master playlist not found for {}:{}. This probably is webrtc stream", getSite().getName(), getName()); throw new StreamNotFoundException("Webrtc streams are not supported for " + getSite().getName()); } - LOG.trace("Loading master playlist {}", masterPlaylistUrl); + log.trace("Loading master playlist {}", masterPlaylistUrl); Request req = new Request.Builder() .url(masterPlaylistUrl) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) @@ -150,7 +150,7 @@ public class CherryTvModel extends AbstractModel { try (Response response = getSite().getHttpClient().execute(req)) { if (response.isSuccessful()) { String body = Objects.requireNonNull(response.body()).string(); - LOG.trace(body); + log.trace(body); InputStream inputStream = new ByteArrayInputStream(body.getBytes(UTF_8)); PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT); Playlist playlist = parser.parse(); @@ -185,13 +185,13 @@ public class CherryTvModel extends AbstractModel { List sources = getStreamSources(); Collections.sort(sources); StreamSource best = sources.get(sources.size() - 1); - resolution = new int[]{best.width, best.height}; + resolution = new int[]{best.getWidth(), best.getHeight()}; } catch (InterruptedException e) { Thread.currentThread().interrupt(); - LOG.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); + log.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); resolution = new int[2]; } catch (ExecutionException | IOException | ParseException | PlaylistException e) { - LOG.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); + log.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); resolution = new int[2]; } } @@ -210,11 +210,11 @@ public class CherryTvModel extends AbstractModel { private boolean followUnfollow(String action, String persistedQueryHash) throws IOException { Request request = createFollowUnfollowRequest(action, persistedQueryHash); - LOG.debug("Sending follow request for model {} with ID {}", getName(), getId()); + log.debug("Sending follow request for model {} with ID {}", getName(), getId()); try (Response response = getSite().getHttpClient().execute(request)) { if (response.isSuccessful()) { String responseBody = Objects.requireNonNull(response.body(), HTTP_RESPONSE_BODY_IS_NULL).string(); - LOG.debug(responseBody); + log.debug(responseBody); JSONObject resp = new JSONObject(responseBody); if (resp.has("data") && !resp.isNull("data")) { JSONObject data = resp.getJSONObject("data"); @@ -227,7 +227,7 @@ public class CherryTvModel extends AbstractModel { return true; } } - LOG.debug(resp.toString(2)); + log.debug(resp.toString(2)); return false; } else { throw new HttpException(response.code(), response.message()); @@ -271,14 +271,6 @@ public class CherryTvModel extends AbstractModel { .build(); } - public void setId(String id) { - this.id = id; - } - - public String getId() { - return id; - } - @Override public void readSiteSpecificData(Map data) { id = data.get("id"); diff --git a/common/src/main/java/ctbrec/sites/dreamcam/DreamcamModel.java b/common/src/main/java/ctbrec/sites/dreamcam/DreamcamModel.java index 2b023c9a..c355a482 100644 --- a/common/src/main/java/ctbrec/sites/dreamcam/DreamcamModel.java +++ b/common/src/main/java/ctbrec/sites/dreamcam/DreamcamModel.java @@ -80,7 +80,7 @@ public class DreamcamModel extends AbstractModel { List sources = new ArrayList<>(); try { StreamSource src = new StreamSource(); - src.mediaPlaylistUrl = getPlaylistUrl(); + src.setMediaPlaylistUrl(getPlaylistUrl()); sources.add(src); } catch (Exception e) { LOG.error("Can not get stream sources for {}: {}", getName(), e.getMessage()); diff --git a/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java b/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java index 7e064609..4c3b4f97 100644 --- a/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java +++ b/common/src/main/java/ctbrec/sites/fc2live/Fc2Model.java @@ -10,12 +10,13 @@ import ctbrec.Config; import ctbrec.io.HttpException; import ctbrec.recorder.download.RecordingProcess; import ctbrec.recorder.download.StreamSource; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import okhttp3.*; import okio.ByteString; import org.json.JSONArray; import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; @@ -27,15 +28,19 @@ import java.util.function.BiConsumer; import static ctbrec.io.HttpConstants.*; +@Slf4j public class Fc2Model extends AbstractModel { - private static final Logger LOG = LoggerFactory.getLogger(Fc2Model.class); + @Getter + @Setter private String id; + @Getter + @Setter private int viewerCount; private boolean online; private String version; - private WebSocket ws; private String playlistUrl; - private AtomicInteger websocketUsage = new AtomicInteger(0); + private transient WebSocket ws; + private final transient AtomicInteger websocketUsage = new AtomicInteger(0); private long lastHeartBeat = System.currentTimeMillis(); private int messageId = 1; @@ -67,7 +72,7 @@ public class Fc2Model extends AbstractModel { if (resp.isSuccessful()) { String msg = resp.body().string(); JSONObject json = new JSONObject(msg); - // LOG.debug(json.toString(2)); + // log.debug(json.toString(2)); JSONObject data = json.getJSONObject("data"); JSONObject channelData = data.getJSONObject("channel_data"); online = channelData.optInt("is_publish") == 1; @@ -101,10 +106,8 @@ public class Fc2Model extends AbstractModel { public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException { try { openWebsocket(); - List sources = new ArrayList<>(); - LOG.debug("Paylist url {}", playlistUrl); - sources.addAll(parseMasterPlaylist(playlistUrl)); - return sources; + log.debug("Paylist url {}", playlistUrl); + return parseMasterPlaylist(playlistUrl); } catch (InterruptedException e1) { Thread.currentThread().interrupt(); throw new ExecutionException(e1); @@ -129,23 +132,22 @@ public class Fc2Model extends AbstractModel { PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8); Playlist playlist = parser.parse(); MasterPlaylist master = playlist.getMasterPlaylist(); - sources.clear(); for (PlaylistData playlistData : master.getPlaylists()) { StreamSource streamsource = new StreamSource(); - streamsource.mediaPlaylistUrl = playlistData.getUri(); + streamsource.setMediaPlaylistUrl(playlistData.getUri()); if (playlistData.hasStreamInfo()) { StreamInfo info = playlistData.getStreamInfo(); - streamsource.bandwidth = info.getBandwidth(); - streamsource.width = info.hasResolution() ? info.getResolution().width : 0; - streamsource.height = info.hasResolution() ? info.getResolution().height : 0; + streamsource.setBandwidth(info.getBandwidth()); + streamsource.setWidth(info.hasResolution() ? info.getResolution().width : 0); + streamsource.setHeight(info.hasResolution() ? info.getResolution().height : 0); } else { - streamsource.bandwidth = 0; - streamsource.width = 0; - streamsource.height = 0; + streamsource.setBandwidth(0); + streamsource.setWidth(0); + streamsource.setHeight(0); } sources.add(streamsource); } - LOG.debug(sources.toString()); + log.debug(sources.toString()); return sources; } else { throw new HttpException(response.code(), response.message()); @@ -172,7 +174,7 @@ public class Fc2Model extends AbstractModel { .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .header(X_REQUESTED_WITH, XML_HTTP_REQUEST) .build(); - LOG.debug("Fetching page {}", url); + log.debug("Fetching page {}", url); try (Response resp = getSite().getHttpClient().execute(req)) { if (resp.isSuccessful()) { String msg = resp.body().string(); @@ -236,48 +238,28 @@ public class Fc2Model extends AbstractModel { JSONObject json = new JSONObject(content); return json.optInt("status") == 1; } else { - LOG.error("Login failed {} {}", resp.code(), resp.message()); + log.error("Login failed {} {}", resp.code(), resp.message()); return false; } } } - public void setId(String id) { - this.id = id; - } - - public String getId() { - return id; - } - - public int getViewerCount() { - return viewerCount; - } - - public void setViewerCount(int viewerCount) { - this.viewerCount = viewerCount; - } - /** * Opens a chat websocket connection. This connection is used to retrieve the HLS playlist url. It also has to be kept open as long as the HLS stream is * "played". Fc2Model keeps track of the number of objects, which tried to open or close the websocket. As long as at least one object is using the * websocket, it is kept open. If the last object, which is using it, calls closeWebsocket, the websocket is closed. - * - * @throws IOException */ public void openWebsocket() throws InterruptedException, IOException { messageId = 1; int usage = websocketUsage.incrementAndGet(); - LOG.debug("{} objects using the websocket for {}", usage, this); - if (ws != null) { - return; - } else { + log.debug("{} objects using the websocket for {}", usage, this); + if (ws == null) { Object monitor = new Object(); loadModelInfo(); getControlToken((token, url) -> { url = url + "?control_token=" + token; - LOG.debug("Session token: {}", token); - LOG.debug("Getting playlist token over websocket {}", url); + log.debug("Session token: {}", token); + log.debug("Getting playlist token over websocket {}", url); Request request = new Request.Builder() .url(url) @@ -302,18 +284,19 @@ public class Fc2Model extends AbstractModel { JSONArray playlists = args.getJSONArray("playlists_high_latency"); JSONObject playlist = playlists.getJSONObject(0); playlistUrl = playlist.getString("url"); - LOG.debug("Master Playlist: {}", playlistUrl); + log.debug("Master Playlist: {}", playlistUrl); synchronized (monitor) { monitor.notifyAll(); } } else { - LOG.trace(json.toString()); + log.trace(json.toString()); } } } else if (json.optString("name").equals("user_count") || json.optString("name").equals("comment")) { // ignore + log.trace("WS <-- {}: {}", getName(), text); } else { - LOG.trace("WS <-- {}: {}", getName(), text); + log.trace("WS <-- {}: {}", getName(), text); } // send heartbeat every now and again @@ -321,24 +304,24 @@ public class Fc2Model extends AbstractModel { if ((now - lastHeartBeat) > TimeUnit.SECONDS.toMillis(30)) { webSocket.send("{\"name\":\"heartbeat\",\"arguments\":{},\"id\":" + messageId + "}"); lastHeartBeat = now; - LOG.trace("Sending heartbeat for {} (messageId: {})", getName(), messageId); + log.trace("Sending heartbeat for {} (messageId: {})", getName(), messageId); messageId++; } } @Override public void onMessage(WebSocket webSocket, ByteString bytes) { - LOG.debug("ws btxt {}", bytes); + log.debug("ws btxt {}", bytes); } @Override public void onClosed(WebSocket webSocket, int code, String reason) { - LOG.debug("ws closed {} - {}", code, reason); + log.debug("ws closed {} - {}", code, reason); } @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) { - LOG.debug("ws failure", t); + log.debug("ws failure", t); response.close(); } }); @@ -355,9 +338,9 @@ public class Fc2Model extends AbstractModel { public void closeWebsocket() { int websocketUsers = websocketUsage.decrementAndGet(); - LOG.debug("{} objects using the websocket for {}", websocketUsers, this); + log.debug("{} objects using the websocket for {}", websocketUsers, this); if (websocketUsers == 0) { - LOG.debug("Closing the websocket for {}", this); + log.debug("Closing the websocket for {}", this); ws.close(1000, ""); ws = null; } diff --git a/common/src/main/java/ctbrec/sites/flirt4free/Flirt4FreeModel.java b/common/src/main/java/ctbrec/sites/flirt4free/Flirt4FreeModel.java index 65bee573..dfa55ab3 100644 --- a/common/src/main/java/ctbrec/sites/flirt4free/Flirt4FreeModel.java +++ b/common/src/main/java/ctbrec/sites/flirt4free/Flirt4FreeModel.java @@ -10,11 +10,12 @@ import ctbrec.Config; import ctbrec.Model; import ctbrec.io.HttpException; import ctbrec.recorder.download.StreamSource; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import okhttp3.*; import org.json.JSONArray; import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; @@ -32,18 +33,22 @@ import static ctbrec.io.HttpConstants.*; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Locale.ENGLISH; +@Slf4j public class Flirt4FreeModel extends AbstractModel { - private static final Logger LOG = LoggerFactory.getLogger(Flirt4FreeModel.class); + @Setter private String id; private String chatHost; private String chatPort; private String chatToken; private String streamHost; + @Setter private String streamUrl; int[] resolution = new int[2]; private final transient Object monitor = new Object(); + @Getter private final transient List categories = new LinkedList<>(); + @Setter private boolean online = false; private boolean isInteractiveShow = false; private boolean isNew = false; @@ -93,7 +98,7 @@ public class Flirt4FreeModel extends AbstractModel { JSONObject json = new JSONObject(body); if (Objects.equals(json.optString("status"), "failed")) { if (Objects.equals(json.optString("message"), "Model is inactive")) { - LOG.debug("Model inactive or deleted: {}", getName()); + log.debug("Model inactive or deleted: {}", getName()); setMarkedForLaterRecording(true); } online = false; @@ -115,17 +120,15 @@ public class Flirt4FreeModel extends AbstractModel { private void updateModelId(JSONObject json) { if (json.has(MODEL_ID)) { Object modelId = json.get(MODEL_ID); - if (modelId instanceof Number n) { - if (n.intValue() > 0) { - id = String.valueOf(json.get(MODEL_ID)); - } + if (modelId instanceof Number n && n.intValue() > 0) { + id = String.valueOf(json.get(MODEL_ID)); } } } private void loadModelInfo() throws IOException { String url = getSite().getBaseUrl() + "/webservices/chat-room-interface.php?a=login_room&model_id=" + id; - LOG.trace("Loading url {}", url); + log.trace("Loading url {}", url); Request request = new Request.Builder() .url(url) .header(ACCEPT, "*/*") @@ -152,7 +155,7 @@ public class Flirt4FreeModel extends AbstractModel { JSONObject user = config.getJSONObject("user"); userIp = user.getString("ip"); } else { - LOG.trace("Loading model info failed. Assuming model {} is offline", getName()); + log.trace("Loading model info failed. Assuming model {} is offline", getName()); online = false; onlineState = Model.State.OFFLINE; } @@ -171,21 +174,14 @@ public class Flirt4FreeModel extends AbstractModel { }; } - @Override public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException { - return getStreamSources(true); - } - - private List getStreamSources(boolean withWebsocket) throws IOException, ExecutionException, ParseException, PlaylistException { MasterPlaylist masterPlaylist; try { - if (withWebsocket) { - acquireSlot(); - try { - loadStreamUrl(); - } finally { - releaseSlot(); - } + acquireSlot(); + try { + loadStreamUrl(); + } finally { + releaseSlot(); } masterPlaylist = getMasterPlaylist(); } catch (InterruptedException e) { @@ -197,12 +193,12 @@ public class Flirt4FreeModel extends AbstractModel { if (playlist.hasStreamInfo()) { StreamSource src = new StreamSource(); StreamInfo info = playlist.getStreamInfo(); - src.bandwidth = info.getBandwidth(); - src.height = (info.hasResolution()) ? info.getResolution().height : 0; - src.width = (info.hasResolution()) ? info.getResolution().width : 0; + src.setBandwidth(info.getBandwidth()); + src.setHeight((info.hasResolution()) ? info.getResolution().height : 0); + src.setWidth((info.hasResolution()) ? info.getResolution().width : 0); HttpUrl masterPlaylistUrl = HttpUrl.parse(streamUrl); - src.mediaPlaylistUrl = "https://" + masterPlaylistUrl.host() + '/' + playlist.getUri(); - LOG.trace("Media playlist {}", src.mediaPlaylistUrl); + src.setMediaPlaylistUrl("https://" + masterPlaylistUrl.host() + '/' + playlist.getUri()); + log.trace("Media playlist {}", src.getMediaPlaylistUrl()); sources.add(src); } } @@ -210,7 +206,7 @@ public class Flirt4FreeModel extends AbstractModel { } public MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException, InterruptedException { - LOG.trace("Loading master playlist {}", streamUrl); + log.trace("Loading master playlist {}", streamUrl); Request req = new Request.Builder() .url(streamUrl) .header(ACCEPT, "*/*") @@ -240,7 +236,7 @@ public class Flirt4FreeModel extends AbstractModel { Objects.requireNonNull(chatHost, "chatHost is null"); String h = chatHost.replace("chat", "chat-vip"); String url = "https://" + h + "/chat?token=" + URLEncoder.encode(chatToken, UTF_8) + "&port_to_be=" + chatPort; - LOG.trace("Opening chat websocket {}", url); + log.trace("Opening chat websocket {}", url); Request req = new Request.Builder() .url(url) .header(ACCEPT, "*/*") @@ -253,16 +249,16 @@ public class Flirt4FreeModel extends AbstractModel { getSite().getHttpClient().newWebSocket(req, new WebSocketListener() { @Override public void onOpen(WebSocket webSocket, Response response) { - LOG.trace("Chat websocket for {} opened", getName()); + log.trace("Chat websocket for {} opened", getName()); } @Override public void onMessage(WebSocket webSocket, String text) { - LOG.trace("Chat wbesocket for {}: {}", getName(), text); + log.trace("Chat wbesocket for {}: {}", getName(), text); JSONObject json = new JSONObject(text); if (json.optString("command").equals("8011")) { JSONObject data = json.getJSONObject("data"); - LOG.trace("stream info:\n{}", data.toString(2)); + log.trace("stream info:\n{}", data.toString(2)); streamHost = data.getString("stream_host"); online = true; isInteractiveShow = data.optString("devices").equals("1"); @@ -277,7 +273,7 @@ public class Flirt4FreeModel extends AbstractModel { resolution[0] = Integer.parseInt(data.getString("stream_width")); resolution[1] = Integer.parseInt(data.getString("stream_height")); } catch (Exception e) { - LOG.warn("Couldn't determine stream resolution", e); + log.warn("Couldn't determine stream resolution", e); } webSocket.close(1000, ""); } @@ -285,7 +281,7 @@ public class Flirt4FreeModel extends AbstractModel { @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) { - LOG.error("Chat websocket for {} failed", getName(), t); + log.error("Chat websocket for {} failed", getName(), t); synchronized (monitor) { monitor.notifyAll(); } @@ -296,7 +292,7 @@ public class Flirt4FreeModel extends AbstractModel { @Override public void onClosed(WebSocket webSocket, int code, String reason) { - LOG.trace("Chat websocket for {} closed {} {}", getName(), code, reason); + log.trace("Chat websocket for {} closed {} {}", getName(), code, reason); synchronized (monitor) { monitor.notifyAll(); } @@ -312,7 +308,7 @@ public class Flirt4FreeModel extends AbstractModel { + "model_id=" + id + "&video_host=" + streamHost + "&t=" + System.currentTimeMillis(); - LOG.debug("Loading master playlist information: {}", url); + log.debug("Loading master playlist information: {}", url); req = new Request.Builder() .url(url) .header(ACCEPT, "*/*") @@ -325,7 +321,7 @@ public class Flirt4FreeModel extends AbstractModel { JSONObject json = new JSONObject(Objects.requireNonNull(response.body(), "HTTP response body is null").string()); JSONArray hls = json.getJSONObject("data").getJSONArray("hls"); streamUrl = "https:" + hls.getJSONObject(0).getString("url"); - LOG.debug("Stream URL is {}", streamUrl); + log.debug("Stream URL is {}", streamUrl); } } } @@ -346,7 +342,7 @@ public class Flirt4FreeModel extends AbstractModel { // send the tip int giftId = isInteractiveShow ? 775 : 171; int amount = tokens.intValue(); - LOG.debug("Sending tip of {} to {}", amount, getName()); + log.debug("Sending tip of {} to {}", amount, getName()); String url = "https://ws.vs3.com/rooms/send-tip.php?" + "gift_id=" + giftId + "&num_credits=" + amount + @@ -355,7 +351,7 @@ public class Flirt4FreeModel extends AbstractModel { "&userIP=" + userIp + "&anonymous=N&response_type=json" + "&t=" + System.currentTimeMillis(); - LOG.debug("Trying to send tip: {}", url); + log.debug("Trying to send tip: {}", url); Request req = new Request.Builder() .url(url) .header(ACCEPT, "*/*") @@ -373,8 +369,8 @@ public class Flirt4FreeModel extends AbstractModel { if (json.has("error_message")) { msg = json.getString("error_message"); } - LOG.error("Sending tip failed: {}", msg); - LOG.debug("Response: {}", json.toString(2)); + log.error("Sending tip failed: {}", msg); + log.debug("Response: {}", json.toString(2)); throw new IOException(msg); } } else { @@ -434,10 +430,10 @@ public class Flirt4FreeModel extends AbstractModel { public int[] getStreamResolution(boolean failFast) throws ExecutionException { if (!failFast && streamUrl != null && resolution[0] == 0) { try { - List streamSources = getStreamSources(true); + List streamSources = getStreamSources(); Collections.sort(streamSources); StreamSource best = streamSources.get(streamSources.size() - 1); - resolution = new int[]{best.width, best.height}; + resolution = new int[]{best.getHeight(), best.getHeight()}; } catch (IOException | ParseException | PlaylistException e) { throw new ExecutionException("Couldn't determine stream resolution", e); } @@ -485,7 +481,7 @@ public class Flirt4FreeModel extends AbstractModel { "&id=" + id + "&name=" + getName() + "&t=" + System.currentTimeMillis(); - LOG.debug("Sending follow/unfollow request: {}", url); + log.debug("Sending follow/unfollow request: {}", url); Request req = new Request.Builder() .url(url) .header(ACCEPT, "*/*") @@ -496,7 +492,7 @@ public class Flirt4FreeModel extends AbstractModel { try (Response response = getSite().getHttpClient().execute(req)) { if (response.isSuccessful()) { String body = response.body().string(); - LOG.debug("Follow/Unfollow response: {}", body); + log.debug("Follow/Unfollow response: {}", body); return Objects.equals(body, "1"); } else { throw new HttpException(response.code(), response.message()); @@ -504,10 +500,6 @@ public class Flirt4FreeModel extends AbstractModel { } } - public void setId(String id) { - this.id = id; - } - @Override public void readSiteSpecificData(Map data) { id = data.get("id"); @@ -518,14 +510,6 @@ public class Flirt4FreeModel extends AbstractModel { data.put("id", id); } - public void setStreamUrl(String streamUrl) { - this.streamUrl = streamUrl; - } - - public void setOnline(boolean b) { - online = b; - } - public boolean isNew() { return isNew; } @@ -557,8 +541,4 @@ public class Flirt4FreeModel extends AbstractModel { lastRequest = System.currentTimeMillis(); requestThrottle.release(); } - - public List getCategories() { - return categories; - } } diff --git a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminStreamRegistration.java b/common/src/main/java/ctbrec/sites/jasmin/LiveJasminStreamRegistration.java index 0643fe2e..dcaac2da 100644 --- a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminStreamRegistration.java +++ b/common/src/main/java/ctbrec/sites/jasmin/LiveJasminStreamRegistration.java @@ -167,7 +167,7 @@ public class LiveJasminStreamRegistration { JSONObject data = message.getJSONArray("data").getJSONArray(0).getJSONObject(0); String streamId = data.getString("streamId"); String wssUrl = data.getJSONObject("protocol").getJSONObject("h5live").getString("wssUrl"); - streamSources.stream().filter(src -> Objects.equals(src.getStreamId(), streamId)).findAny().ifPresent(src -> src.mediaPlaylistUrl = wssUrl); + streamSources.stream().filter(src -> Objects.equals(src.getStreamId(), streamId)).findAny().ifPresent(src -> src.setMediaPlaylistUrl(wssUrl)); if (--streamCount == 0) { awaitBarrier(); } @@ -230,10 +230,10 @@ public class LiveJasminStreamRegistration { .replace("{streamName}", URLEncoder.encode(streamName, UTF_8)); LiveJasminStreamSource streamSource = new LiveJasminStreamSource(); - streamSource.mediaPlaylistUrl = hlsUrl; - streamSource.width = w; - streamSource.height = h; - streamSource.bandwidth = bitrate; + streamSource.setMediaPlaylistUrl(hlsUrl); + streamSource.setWidth(w); + streamSource.setHeight(h); + streamSource.setBandwidth(bitrate); streamSource.setRtmpUrl(rtmpUrl); streamSource.setStreamName(streamName); streamSource.setStreamId(streamId); diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java index ff7b9ef4..dad1e995 100644 --- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java +++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java @@ -9,6 +9,8 @@ import ctbrec.io.HttpException; import ctbrec.recorder.download.RecordingProcess; import ctbrec.recorder.download.StreamSource; import ctbrec.sites.ModelOfflineException; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import okhttp3.Request; import okhttp3.Response; @@ -36,6 +38,8 @@ public class MVLiveModel extends AbstractModel { private transient JSONObject roomLocation; private transient Instant lastRoomLocationUpdate = Instant.EPOCH; private String roomNumber; + @Getter + @Setter private String id; @Override @@ -74,16 +78,16 @@ public class MVLiveModel extends AbstractModel { for (PlaylistData playlist : masterPlaylist.getPlaylists()) { if (playlist.hasStreamInfo()) { StreamSource src = new StreamSource(); - src.bandwidth = playlist.getStreamInfo().getBandwidth(); - src.height = playlist.getStreamInfo().getResolution().height; + src.setBandwidth(playlist.getStreamInfo().getBandwidth()); + src.setHeight(playlist.getStreamInfo().getResolution().height); String masterUrl = streamLocation.masterPlaylist; String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1); String segmentUri = baseUrl + playlist.getUri(); - src.mediaPlaylistUrl = segmentUri; - if (src.mediaPlaylistUrl.contains("?")) { - src.mediaPlaylistUrl = src.mediaPlaylistUrl.substring(0, src.mediaPlaylistUrl.lastIndexOf('?')); + src.setMediaPlaylistUrl(segmentUri); + if (src.getMediaPlaylistUrl().contains("?")) { + src.setMediaPlaylistUrl((src.getMediaPlaylistUrl().substring(0, src.getMediaPlaylistUrl().lastIndexOf('?')))); } - log.debug("Media playlist {}", src.mediaPlaylistUrl); + log.debug("Media playlist {}", src.getMediaPlaylistUrl()); sources.add(src); } } @@ -247,14 +251,6 @@ public class MVLiveModel extends AbstractModel { id = data.get("id"); } - public void setId(String id) { - this.id = id; - } - - public String getId() { - return id; - } - public void updateStateFromJson(JSONObject creator) { setId(creator.getString("id")); setDisplayName(creator.optString("display_name", null)); diff --git a/common/src/main/java/ctbrec/sites/mfc/DashStreamSourceProvider.java b/common/src/main/java/ctbrec/sites/mfc/DashStreamSourceProvider.java index 0286dfc9..90c1b68b 100644 --- a/common/src/main/java/ctbrec/sites/mfc/DashStreamSourceProvider.java +++ b/common/src/main/java/ctbrec/sites/mfc/DashStreamSourceProvider.java @@ -1,22 +1,5 @@ package ctbrec.sites.mfc; -import static ctbrec.io.HttpConstants.*; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import ctbrec.Config; import ctbrec.recorder.download.StreamSource; import ctbrec.recorder.download.dash.AdaptationSetType; @@ -26,14 +9,28 @@ import ctbrec.recorder.download.dash.RepresentationType; import ctbrec.sites.Site; import okhttp3.Request; import okhttp3.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static ctbrec.io.HttpConstants.*; public class DashStreamSourceProvider implements StreamSourceProvider { private static final Logger LOG = LoggerFactory.getLogger(DashStreamSourceProvider.class); - private Config config; + private final Config config; - private Site site; + private final Site site; public DashStreamSourceProvider(Config config, Site site) { this.config = config; @@ -65,12 +62,12 @@ public class DashStreamSourceProvider implements StreamSourceProvider { return videoStreams.stream().map(ast -> { RepresentationType representation = ast.getRepresentation().get(0); StreamSource src = new StreamSource(); - src.width = ast.getWidth().intValue(); - src.height = ast.getHeight().intValue(); - src.bandwidth = (int)representation.getBandwidth(); - src.mediaPlaylistUrl = streamUrl; + src.setWidth(ast.getWidth().intValue()); + src.setHeight(ast.getHeight().intValue()); + src.setBandwidth((int) representation.getBandwidth()); + src.setMediaPlaylistUrl(streamUrl); return src; - }).collect(Collectors.toList()); + }).toList(); } } diff --git a/common/src/main/java/ctbrec/sites/mfc/HlsStreamSourceProvider.java b/common/src/main/java/ctbrec/sites/mfc/HlsStreamSourceProvider.java index 01bf6e4a..19afcb19 100644 --- a/common/src/main/java/ctbrec/sites/mfc/HlsStreamSourceProvider.java +++ b/common/src/main/java/ctbrec/sites/mfc/HlsStreamSourceProvider.java @@ -1,6 +1,17 @@ package ctbrec.sites.mfc; -import static ctbrec.io.HttpConstants.*; +import com.iheartradio.m3u8.*; +import com.iheartradio.m3u8.data.MasterPlaylist; +import com.iheartradio.m3u8.data.Playlist; +import com.iheartradio.m3u8.data.PlaylistData; +import ctbrec.Config; +import ctbrec.io.HttpClient; +import ctbrec.io.HttpException; +import ctbrec.recorder.download.StreamSource; +import okhttp3.Request; +import okhttp3.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; @@ -10,25 +21,7 @@ import java.util.Locale; import java.util.Objects; import java.util.concurrent.ExecutionException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.iheartradio.m3u8.Encoding; -import com.iheartradio.m3u8.Format; -import com.iheartradio.m3u8.ParseException; -import com.iheartradio.m3u8.ParsingMode; -import com.iheartradio.m3u8.PlaylistException; -import com.iheartradio.m3u8.PlaylistParser; -import com.iheartradio.m3u8.data.MasterPlaylist; -import com.iheartradio.m3u8.data.Playlist; -import com.iheartradio.m3u8.data.PlaylistData; - -import ctbrec.Config; -import ctbrec.io.HttpClient; -import ctbrec.io.HttpException; -import ctbrec.recorder.download.StreamSource; -import okhttp3.Request; -import okhttp3.Response; +import static ctbrec.io.HttpConstants.*; public record HlsStreamSourceProvider(HttpClient httpClient) implements StreamSourceProvider { @@ -41,19 +34,19 @@ public record HlsStreamSourceProvider(HttpClient httpClient) implements StreamSo for (PlaylistData playlist : masterPlaylist.getPlaylists()) { if (playlist.hasStreamInfo()) { StreamSource src = new StreamSource(); - src.bandwidth = playlist.getStreamInfo().getBandwidth(); + src.setBandwidth(playlist.getStreamInfo().getBandwidth()); if (playlist.getStreamInfo().getResolution() != null) { - src.width = playlist.getStreamInfo().getResolution().width; - src.height = playlist.getStreamInfo().getResolution().height; + src.setWidth(playlist.getStreamInfo().getResolution().width); + src.setHeight(playlist.getStreamInfo().getResolution().height); } else { - src.width = StreamSource.UNKNOWN; - src.height = StreamSource.UNKNOWN; + src.setWidth(StreamSource.UNKNOWN); + src.setHeight(StreamSource.UNKNOWN); } String masterUrl = streamUrl; String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1); String segmentUri = baseUrl + playlist.getUri(); - src.mediaPlaylistUrl = segmentUri; - LOG.trace("Media playlist {}", src.mediaPlaylistUrl); + src.setMediaPlaylistUrl(segmentUri); + LOG.trace("Media playlist {}", src.getMediaPlaylistUrl()); sources.add(src); } } diff --git a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java index a0d278cb..1ab56e8b 100644 --- a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java +++ b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java @@ -10,6 +10,8 @@ import ctbrec.recorder.download.HttpHeaderFactory; import ctbrec.recorder.download.HttpHeaderFactoryImpl; import ctbrec.recorder.download.RecordingProcess; import ctbrec.recorder.download.StreamSource; +import lombok.Getter; +import lombok.Setter; import okhttp3.FormBody; import okhttp3.Request; import okhttp3.RequestBody; @@ -32,9 +34,17 @@ public class MyFreeCamsModel extends AbstractModel { private static final Logger LOG = LoggerFactory.getLogger(MyFreeCamsModel.class); + @Getter + @Setter private int uid = -1; // undefined + @Getter + @Setter private String streamUrl; + @Getter + @Setter private double camScore; + @Getter + @Setter private int viewerCount; private ctbrec.sites.mfc.State state; private int[] resolution = new int[2]; @@ -84,21 +94,17 @@ public class MyFreeCamsModel extends AbstractModel { } } - switch (state) { - case ONLINE, RECORDING: - return ctbrec.Model.State.ONLINE; - case AWAY: - return ctbrec.Model.State.AWAY; - case PRIVATE: - return ctbrec.Model.State.PRIVATE; - case GROUP_SHOW: - return ctbrec.Model.State.GROUP; - case OFFLINE, CAMOFF, UNKNOWN: - return ctbrec.Model.State.OFFLINE; - default: + return switch (state) { + case ONLINE, RECORDING -> State.ONLINE; + case AWAY -> State.AWAY; + case PRIVATE -> State.PRIVATE; + case GROUP_SHOW -> State.GROUP; + case OFFLINE, CAMOFF, UNKNOWN -> State.OFFLINE; + default -> { LOG.debug("State {} is not mapped", this.state); - return ctbrec.Model.State.UNKNOWN; - } + yield State.UNKNOWN; + } + }; } @Override @@ -182,7 +188,7 @@ public class MyFreeCamsModel extends AbstractModel { List streamSources = getStreamSources(); Collections.sort(streamSources); StreamSource best = streamSources.get(streamSources.size() - 1); - resolution = new int[]{best.width, best.height}; + resolution = new int[]{best.getWidth(), best.getHeight()}; } catch (JAXBException | ParseException | PlaylistException e) { LOG.warn("Couldn't determine stream resolution - {}", e.getMessage()); } catch (ExecutionException | IOException e) { @@ -192,22 +198,6 @@ public class MyFreeCamsModel extends AbstractModel { return resolution; } - public void setStreamUrl(String streamUrl) { - this.streamUrl = streamUrl; - } - - public String getStreamUrl() { - return streamUrl; - } - - public double getCamScore() { - return camScore; - } - - public void setCamScore(double camScore) { - this.camScore = camScore; - } - public boolean isNew() { MyFreeCams mfc = (MyFreeCams) getSite(); SessionState sessionState = mfc.getClient().getSessionState(this); @@ -292,7 +282,7 @@ public class MyFreeCamsModel extends AbstractModel { } private int toCamServ(String server) { - return Integer.parseInt(server.replaceAll("[^0-9]+", "")); + return Integer.parseInt(server.replaceAll("\\D+", "")); } @Override @@ -305,22 +295,6 @@ public class MyFreeCamsModel extends AbstractModel { return ((MyFreeCams) site).getClient().unfollow(getUid()); } - public int getUid() { - return uid; - } - - public void setUid(int uid) { - this.uid = uid; - } - - public int getViewerCount() { - return viewerCount; - } - - public void setViewerCount(int viewerCount) { - this.viewerCount = viewerCount; - } - @Override public void readSiteSpecificData(Map data) { uid = Integer.parseInt(data.get("uid")); @@ -337,11 +311,6 @@ public class MyFreeCamsModel extends AbstractModel { updateStreamUrl(); } return super.createDownload(); - // if(isHlsStream()) { - // return super.createDownload(); - // } else { - // return new MyFreeCamsWebrtcDownload(uid, streamUrl, ((MyFreeCams)site).getClient()); - // } } @Override diff --git a/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsModel.java b/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsModel.java index 16721cc5..7778776c 100644 --- a/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsModel.java +++ b/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsModel.java @@ -90,9 +90,9 @@ public class SecretFriendsModel extends AbstractModel { .build(); StreamSource src = new StreamSource(); - src.width = 1280; - src.height = 720; - src.mediaPlaylistUrl = wsUrl.toString(); + src.setWidth(1280); + src.setHeight(720); + src.setMediaPlaylistUrl(wsUrl.toString()); return Collections.singletonList(src); } diff --git a/common/src/main/java/ctbrec/sites/showup/ShowupModel.java b/common/src/main/java/ctbrec/sites/showup/ShowupModel.java index 88598448..436d7ae3 100644 --- a/common/src/main/java/ctbrec/sites/showup/ShowupModel.java +++ b/common/src/main/java/ctbrec/sites/showup/ShowupModel.java @@ -10,6 +10,8 @@ import ctbrec.recorder.download.HttpHeaderFactory; import ctbrec.recorder.download.HttpHeaderFactoryImpl; import ctbrec.recorder.download.RecordingProcess; import ctbrec.recorder.download.StreamSource; +import lombok.Getter; +import lombok.Setter; import javax.xml.bind.JAXBException; import java.io.IOException; @@ -21,11 +23,18 @@ import static ctbrec.Model.State.*; import static ctbrec.io.HttpConstants.*; public class ShowupModel extends AbstractModel { + private static final Random RNG = new Random(); + @Getter + @Setter private String uid; + @Getter + @Setter private String streamId; + @Getter + @Setter private String streamTranscoderAddr; - private int[] resolution = new int[2]; + private final int[] resolution = new int[2]; @Override public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { @@ -54,8 +63,8 @@ public class ShowupModel extends AbstractModel { @Override public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException, JAXBException { StreamSource src = new StreamSource(); - src.width = 480; - src.height = 360; + src.setWidth(480); + src.setHeight(360); if (streamId == null || streamTranscoderAddr == null) { List modelList = getShowupSite().getModelList(); @@ -68,11 +77,11 @@ public class ShowupModel extends AbstractModel { } } - int cdnHost = 1 + new Random().nextInt(5); - int cid = 100_000 + new Random().nextInt(900_000); - long pid = 10_000_000_000L + new Random().nextInt(); + int cdnHost = 1 + RNG.nextInt(5); + int cid = 100_000 + RNG.nextInt(900_000); + long pid = 10_000_000_000L + RNG.nextInt(); String urlTemplate = "https://cdn-e0{0}.showup.tv/h5live/http/playlist.m3u8?url=rtmp%3A%2F%2F{1}%3A1935%2Fwebrtc&stream={2}_aac&cid={3}&pid={4}"; - src.mediaPlaylistUrl = MessageFormat.format(urlTemplate, cdnHost, streamTranscoderAddr, streamId, cid, pid); + src.setMediaPlaylistUrl(MessageFormat.format(urlTemplate, cdnHost, streamTranscoderAddr, streamId, cid, pid)); List sources = new ArrayList<>(); sources.add(src); return sources; @@ -107,30 +116,6 @@ public class ShowupModel extends AbstractModel { return (Showup) getSite(); } - public String getStreamId() { - return streamId; - } - - public void setStreamId(String streamId) { - this.streamId = streamId; - } - - public String getStreamTranscoderAddr() { - return streamTranscoderAddr; - } - - public void setStreamTranscoderAddr(String streamTranscoderAddr) { - this.streamTranscoderAddr = streamTranscoderAddr; - } - - public String getUid() { - return uid; - } - - public void setUid(String uid) { - this.uid = uid; - } - @Override public RecordingProcess createDownload() { return new ShowupWebrtcDownload(getSite().getHttpClient()); @@ -165,9 +150,9 @@ public class ShowupModel extends AbstractModel { } } - int cdnHost = 1 + new Random().nextInt(5); - int cid = 100_000 + new Random().nextInt(900_000); - long pid = 10_000_000_000L + new Random().nextInt(); + int cdnHost = 1 + RNG.nextInt(5); + int cid = 100_000 + RNG.nextInt(900_000); + long pid = 10_000_000_000L + RNG.nextInt(); String urlTemplate = "https://cdn-e0{0}.showup.tv/h5live/stream/?url=rtmp%3A%2F%2F{1}%3A1935%2Fwebrtc&stream={2}_aac&cid={3,number,#}&pid={4,number,#}"; return MessageFormat.format(urlTemplate, cdnHost, streamTranscoderAddr, streamId, cid, pid); } diff --git a/common/src/main/java/ctbrec/sites/streamate/StreamateModel.java b/common/src/main/java/ctbrec/sites/streamate/StreamateModel.java index 1d8d1305..1158177f 100644 --- a/common/src/main/java/ctbrec/sites/streamate/StreamateModel.java +++ b/common/src/main/java/ctbrec/sites/streamate/StreamateModel.java @@ -7,13 +7,14 @@ import ctbrec.Config; import ctbrec.NotImplementedExcetion; import ctbrec.io.HttpException; import ctbrec.recorder.download.StreamSource; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import org.json.JSONArray; import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.URLEncoder; @@ -26,17 +27,18 @@ import static ctbrec.io.HttpConstants.*; import static ctbrec.sites.streamate.StreamateHttpClient.JSON; import static java.nio.charset.StandardCharsets.UTF_8; +@Slf4j public class StreamateModel extends AbstractModel { private static final String ORIGIN = "origin"; - - private static final Logger LOG = LoggerFactory.getLogger(StreamateModel.class); - private static final Long MODEL_ID_UNDEFINED = -1L; + @Setter private boolean online = false; private final transient List streamSources = new ArrayList<>(); private int[] resolution; + @Getter + @Setter private Long id = MODEL_ID_UNDEFINED; @Override @@ -57,10 +59,6 @@ public class StreamateModel extends AbstractModel { return online; } - public void setOnline(boolean online) { - this.online = online; - } - @Override public State getOnlineState(boolean failFast) throws IOException, ExecutionException { if (!failFast && onlineState == UNKNOWN) { @@ -92,10 +90,10 @@ public class StreamateModel extends AbstractModel { for (int i = 0; i < encodings.length(); i++) { JSONObject encoding = encodings.getJSONObject(i); StreamSource src = new StreamSource(); - src.mediaPlaylistUrl = encoding.getString("location"); - src.width = encoding.optInt("videoWidth"); - src.height = encoding.optInt("videoHeight"); - src.bandwidth = (encoding.optInt("videoKbps") + encoding.optInt("audioKbps")) * 1024; + src.setMediaPlaylistUrl(encoding.getString("location")); + src.setWidth(encoding.optInt("videoWidth")); + src.setHeight(encoding.optInt("videoHeight")); + src.setBandwidth((encoding.optInt("videoKbps") + encoding.optInt("audioKbps")) * 1024); streamSources.add(src); } @@ -103,11 +101,10 @@ public class StreamateModel extends AbstractModel { if (hls.has(ORIGIN) && !hls.isNull(ORIGIN)) { JSONObject origin = hls.getJSONObject(ORIGIN); StreamSource src = new StreamSource(); - src.mediaPlaylistUrl = origin.getString("location"); - src.width = origin.optInt("videoWidth"); - src.height = origin.optInt("videoHeight"); - src.bandwidth = (origin.optInt("videoKbps") + origin.optInt("audioKbps")) * 1024; - src.height = StreamSource.ORIGIN; + src.setMediaPlaylistUrl(origin.getString("location")); + src.setWidth(origin.optInt("videoWidth")); + src.setBandwidth((origin.optInt("videoKbps") + origin.optInt("audioKbps")) * 1024); + src.setHeight(StreamSource.ORIGIN); streamSources.add(src); } @@ -155,15 +152,15 @@ public class StreamateModel extends AbstractModel { List sources = getStreamSources(); Collections.sort(sources); StreamSource best = sources.get(sources.size() - 1); - if (best.height == StreamSource.ORIGIN) { + if (best.getHeight() == StreamSource.ORIGIN) { best = sources.get(sources.size() - 2); } - resolution = new int[]{best.width, best.height}; + resolution = new int[]{best.getWidth(), best.getHeight()}; } catch (InterruptedException e) { - LOG.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); + log.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); Thread.currentThread().interrupt(); } catch (ExecutionException | IOException | ParseException | PlaylistException e) { - LOG.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); + log.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); } } return resolution; @@ -213,14 +210,6 @@ public class StreamateModel extends AbstractModel { } } - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - @Override public void readSiteSpecificData(Map data) { id = Long.parseLong(data.get("id")); @@ -232,7 +221,7 @@ public class StreamateModel extends AbstractModel { try { loadModelId(); } catch (IOException e) { - LOG.error("Couldn't load model ID for {}. This can cause problems with saving / loading the model", getName(), e); + log.error("Couldn't load model ID for {}. This can cause problems with saving / loading the model", getName(), e); } } data.put("id", Long.toString(id)); diff --git a/common/src/main/java/ctbrec/sites/streamray/StreamrayModel.java b/common/src/main/java/ctbrec/sites/streamray/StreamrayModel.java index 3e92b78e..f5d1b4c4 100644 --- a/common/src/main/java/ctbrec/sites/streamray/StreamrayModel.java +++ b/common/src/main/java/ctbrec/sites/streamray/StreamrayModel.java @@ -89,10 +89,10 @@ public class StreamrayModel extends AbstractModel { try { String url = getMasterPlaylistUrl(); StreamSource src = new StreamSource(); - src.mediaPlaylistUrl = url; - src.height = 0; - src.width = 0; - src.bandwidth = 0; + src.setMediaPlaylistUrl(url); + src.setHeight(0); + src.setWidth(0); + src.setBandwidth(0); sources.add(src); } catch (IOException e) { log.error("Can not get stream sources for {}", getName()); diff --git a/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java b/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java index 5a481526..f88dee5b 100644 --- a/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java +++ b/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java @@ -11,13 +11,12 @@ import ctbrec.recorder.download.RecordingProcess; import ctbrec.recorder.download.StreamSource; import ctbrec.recorder.download.hls.HlsdlDownload; import ctbrec.recorder.download.hls.MergedFfmpegHlsDownload; +import lombok.extern.slf4j.Slf4j; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import org.json.JSONArray; import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -33,9 +32,9 @@ import static ctbrec.io.HttpConstants.*; import static ctbrec.sites.stripchat.StripchatHttpClient.JSON; import static java.nio.charset.StandardCharsets.UTF_8; +@Slf4j public class StripchatModel extends AbstractModel { - private static final Logger LOG = LoggerFactory.getLogger(StripchatModel.class); - + private static final Random RNG = new Random(); private int[] resolution = new int[]{0, 0}; private int modelId = 0; private boolean isVr = false; @@ -52,7 +51,7 @@ public class StripchatModel extends AbstractModel { String status = user.optString("status"); mapOnlineState(status); if (isBanned(user)) { - LOG.debug("Model inactive or deleted: {}", getName()); + log.debug("Model inactive or deleted: {}", getName()); setMarkedForLaterRecording(true); } modelId = user.optInt("id"); @@ -75,7 +74,7 @@ public class StripchatModel extends AbstractModel { case "private", "p2p", "groupShow", "virtualPrivate" -> setOnlineState(PRIVATE); case "off" -> setOnlineState(OFFLINE); default -> { - LOG.debug("Unknown online state {} for model {}", status, getName()); + log.debug("Unknown online state {} for model {}", status, getName()); setOnlineState(OFFLINE); } } @@ -122,14 +121,15 @@ public class StripchatModel extends AbstractModel { for (StreamSource original : extractStreamSources(masterPlaylist)) { boolean found = false; for (StreamSource source : streamSources) { - if (source.height == original.height) { + if (source.getHeight() == original.getHeight()) { found = true; + break; } } if (!found) streamSources.add(original); } } catch (Exception e) { - LOG.warn("Original stream quality not available", e); + log.warn("Original stream quality not available", e); } return streamSources; } @@ -139,13 +139,13 @@ public class StripchatModel extends AbstractModel { for (PlaylistData playlist : masterPlaylist.getPlaylists()) { if (playlist.hasStreamInfo()) { StreamSource src = new StreamSource(); - src.bandwidth = playlist.getStreamInfo().getBandwidth(); - src.height = playlist.getStreamInfo().getResolution().height; - src.mediaPlaylistUrl = playlist.getUri(); - if (src.mediaPlaylistUrl.contains("?")) { - src.mediaPlaylistUrl = src.mediaPlaylistUrl.substring(0, src.mediaPlaylistUrl.lastIndexOf('?')); + src.setBandwidth(playlist.getStreamInfo().getBandwidth()); + src.setHeight(playlist.getStreamInfo().getResolution().height); + src.setMediaPlaylistUrl(playlist.getUri()); + if (src.getMediaPlaylistUrl().contains("?")) { + src.setMediaPlaylistUrl(src.getMediaPlaylistUrl().substring(0, src.getMediaPlaylistUrl().lastIndexOf('?'))); } - LOG.trace("Media playlist {}", src.mediaPlaylistUrl); + log.trace("Media playlist {}", src.getMediaPlaylistUrl()); sources.add(src); } } @@ -153,7 +153,7 @@ public class StripchatModel extends AbstractModel { } private MasterPlaylist getMasterPlaylist(String url) throws IOException, ParseException, PlaylistException { - LOG.trace("Loading master playlist {}", url); + log.trace("Loading master playlist {}", url); Request req = new Request.Builder() .url(url) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) @@ -161,7 +161,7 @@ public class StripchatModel extends AbstractModel { try (Response response = getSite().getHttpClient().execute(req)) { if (response.isSuccessful()) { String body = response.body().string(); - LOG.trace(body); + log.trace(body); InputStream inputStream = new ByteArrayInputStream(body.getBytes(UTF_8)); PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT); Playlist playlist = parser.parse(); @@ -174,9 +174,9 @@ public class StripchatModel extends AbstractModel { } private String getMasterPlaylistUrl() throws IOException { - boolean VR = Config.getInstance().getSettings().stripchatVR; + boolean isVirtualRealityStream = Config.getInstance().getSettings().stripchatVR; String hlsUrlTemplate = "https://edge-hls.doppiocdn.com/hls/{0}{1}/master/{0}{1}_auto.m3u8?playlistType=Standart"; - String vrSuffix = (VR && isVr) ? "_vr" : ""; + String vrSuffix = (isVirtualRealityStream && isVr) ? "_vr" : ""; if (modelId > 0) { return MessageFormat.format(hlsUrlTemplate, String.valueOf(modelId), vrSuffix); } @@ -193,12 +193,12 @@ public class StripchatModel extends AbstractModel { try (Response response = site.getHttpClient().execute(req)) { if (response.isSuccessful()) { String body = response.body().string(); - LOG.trace(body); + log.trace(body); JSONObject jsonResponse = new JSONObject(body); String streamName = jsonResponse.optString("streamName"); JSONObject broadcastSettings = jsonResponse.getJSONObject("broadcastSettings"); String vrBroadcastServer = broadcastSettings.optString("vrBroadcastServer"); - vrSuffix = (!VR || vrBroadcastServer.isEmpty()) ? "" : "_vr"; + vrSuffix = (!isVirtualRealityStream || vrBroadcastServer.isEmpty()) ? "" : "_vr"; return MessageFormat.format(hlsUrlTemplate, streamName, vrSuffix); } else { throw new HttpException(response.code(), response.message()); @@ -238,12 +238,12 @@ public class StripchatModel extends AbstractModel { @Override public boolean follow() throws IOException { getSite().getHttpClient().login(); - JSONObject modelInfo = getModelInfo(); - JSONObject user = modelInfo.getJSONObject("user"); - long modelId = user.optLong("id"); + JSONObject info = getModelInfo(); + JSONObject user = info.getJSONObject("user"); + long id = user.optLong("id"); StripchatHttpClient client = (StripchatHttpClient) getSite().getHttpClient(); - String url = Stripchat.baseUri + "/api/front/users/" + client.getUserId() + "/favorites/" + modelId; + String url = Stripchat.baseUri + "/api/front/users/" + client.getUserId() + "/favorites/" + id; JSONObject requestParams = new JSONObject(); requestParams.put("csrfToken", client.getCsrfToken()); requestParams.put("csrfTimestamp", client.getCsrfTimestamp()); @@ -272,11 +272,11 @@ public class StripchatModel extends AbstractModel { @Override public boolean unfollow() throws IOException { getSite().getHttpClient().login(); - JSONObject modelInfo = getModelInfo(); - JSONObject user = modelInfo.getJSONObject("user"); - long modelId = user.optLong("id"); + JSONObject info = getModelInfo(); + JSONObject user = info.getJSONObject("user"); + long id = user.optLong("id"); JSONArray favoriteIds = new JSONArray(); - favoriteIds.put(modelId); + favoriteIds.put(id); StripchatHttpClient client = (StripchatHttpClient) getSite().getHttpClient(); String url = Stripchat.baseUri + "/api/front/users/" + client.getUserId() + "/favorites"; @@ -320,7 +320,7 @@ public class StripchatModel extends AbstractModel { if (jsonResponse.has("user")) { JSONObject user = jsonResponse.getJSONObject("user"); if (isBanned(user)) { - LOG.debug("Model inactive or deleted: {}", getName()); + log.debug("Model inactive or deleted: {}", getName()); return false; } } @@ -337,11 +337,10 @@ public class StripchatModel extends AbstractModel { } protected String getUniq() { - Random r = new Random(); String dict = "0123456789abcdefghijklmnopqarstvwxyz"; char[] text = new char[16]; for (int i = 0; i < 16; i++) { - text[i] = dict.charAt(r.nextInt(dict.length())); + text[i] = dict.charAt(RNG.nextInt(dict.length())); } return new String(text); } diff --git a/common/src/main/java/ctbrec/sites/winktv/WinkTvModel.java b/common/src/main/java/ctbrec/sites/winktv/WinkTvModel.java index 1795e3aa..aae1fa37 100644 --- a/common/src/main/java/ctbrec/sites/winktv/WinkTvModel.java +++ b/common/src/main/java/ctbrec/sites/winktv/WinkTvModel.java @@ -35,6 +35,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; @Slf4j public class WinkTvModel extends AbstractModel { + private static final String KEY_MEDIA = "media"; private int[] resolution = new int[]{0, 0}; @Getter @@ -49,8 +50,8 @@ public class WinkTvModel extends AbstractModel { if (ignoreCache) { try { JSONObject json = getModelInfo(); - if (json.has("media")) { - JSONObject media = json.getJSONObject("media"); + if (json.has(KEY_MEDIA)) { + JSONObject media = json.getJSONObject(KEY_MEDIA); boolean isLive = media.optBoolean("isLive"); String meType = media.optString("type"); if (isLive && meType.equals("free")) { @@ -70,9 +71,7 @@ public class WinkTvModel extends AbstractModel { @Override public State getOnlineState(boolean failFast) throws IOException, ExecutionException { - if (failFast && onlineState != UNKNOWN) { - return onlineState; - } else { + if (!failFast || onlineState == UNKNOWN) { try { onlineState = isOnline(true) ? ONLINE : OFFLINE; } catch (InterruptedException e) { @@ -81,8 +80,8 @@ public class WinkTvModel extends AbstractModel { } catch (IOException | ExecutionException e) { onlineState = OFFLINE; } - return onlineState; } + return onlineState; } @Override @@ -98,10 +97,10 @@ public class WinkTvModel extends AbstractModel { for (PlaylistData playlist : masterPlaylist.getPlaylists()) { if (playlist.hasStreamInfo()) { StreamSource src = new StreamSource(); - src.bandwidth = playlist.getStreamInfo().getBandwidth(); - src.height = playlist.getStreamInfo().getResolution().height; - src.mediaPlaylistUrl = playlist.getUri(); - log.trace("Media playlist {}", src.mediaPlaylistUrl); + src.setBandwidth(playlist.getStreamInfo().getBandwidth()); + src.setHeight(playlist.getStreamInfo().getResolution().height); + src.setMediaPlaylistUrl(playlist.getUri()); + log.trace("Media playlist {}", src.getMediaPlaylistUrl()); sources.add(src); } } @@ -190,7 +189,7 @@ public class WinkTvModel extends AbstractModel { String url = "https://api.winktv.co.kr/v1/member/bj"; FormBody body = new FormBody.Builder() .add("userId", getName()) - .add("info", "media") + .add("info", KEY_MEDIA) .build(); Request req = new Request.Builder() .url(url) diff --git a/common/src/main/java/ctbrec/sites/xlovecam/XloveCamModel.java b/common/src/main/java/ctbrec/sites/xlovecam/XloveCamModel.java index 3e347dca..038fd634 100644 --- a/common/src/main/java/ctbrec/sites/xlovecam/XloveCamModel.java +++ b/common/src/main/java/ctbrec/sites/xlovecam/XloveCamModel.java @@ -1,9 +1,18 @@ package ctbrec.sites.xlovecam; -import static ctbrec.Model.State.*; -import static ctbrec.io.HttpConstants.*; -import static java.nio.charset.StandardCharsets.*; +import com.iheartradio.m3u8.*; +import com.iheartradio.m3u8.data.MasterPlaylist; +import com.iheartradio.m3u8.data.Playlist; +import com.iheartradio.m3u8.data.PlaylistData; +import ctbrec.AbstractModel; +import ctbrec.Config; +import ctbrec.io.HttpException; +import ctbrec.recorder.download.StreamSource; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Request; +import okhttp3.Response; +import javax.xml.bind.JAXBException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -14,31 +23,13 @@ import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.xml.bind.JAXBException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.iheartradio.m3u8.Encoding; -import com.iheartradio.m3u8.Format; -import com.iheartradio.m3u8.ParseException; -import com.iheartradio.m3u8.ParsingMode; -import com.iheartradio.m3u8.PlaylistException; -import com.iheartradio.m3u8.PlaylistParser; -import com.iheartradio.m3u8.data.MasterPlaylist; -import com.iheartradio.m3u8.data.Playlist; -import com.iheartradio.m3u8.data.PlaylistData; - -import ctbrec.AbstractModel; -import ctbrec.Config; -import ctbrec.io.HttpException; -import ctbrec.recorder.download.StreamSource; -import okhttp3.Request; -import okhttp3.Response; +import static ctbrec.Model.State.*; +import static ctbrec.io.HttpConstants.USER_AGENT; +import static java.nio.charset.StandardCharsets.UTF_8; +@Slf4j public class XloveCamModel extends AbstractModel { - private static final Logger LOG = LoggerFactory.getLogger(XloveCamModel.class); private static final Pattern HLS_PLAYLIST_PATTERN = Pattern.compile("\"hlsPlaylist\":\"(.*?)\","); private boolean online = false; @@ -56,9 +47,7 @@ public class XloveCamModel extends AbstractModel { @Override public State getOnlineState(boolean failFast) throws IOException, ExecutionException { - if (failFast && onlineState != UNKNOWN) { - return onlineState; - } else { + if (!failFast || onlineState == UNKNOWN) { try { onlineState = isOnline(true) ? ONLINE : OFFLINE; } catch (InterruptedException e) { @@ -67,8 +56,8 @@ public class XloveCamModel extends AbstractModel { } catch (IOException | ExecutionException e) { onlineState = OFFLINE; } - return onlineState; } + return onlineState; } @Override @@ -78,10 +67,10 @@ public class XloveCamModel extends AbstractModel { for (PlaylistData playlist : masterPlaylist.getPlaylists()) { if (playlist.hasStreamInfo()) { StreamSource src = new StreamSource(); - src.bandwidth = playlist.getStreamInfo().getBandwidth(); - src.height = Optional.ofNullable(playlist.getStreamInfo().getResolution()).map(r -> r.height).orElse(0); - src.mediaPlaylistUrl = playlist.getUri(); - LOG.trace("Media playlist {}", src.mediaPlaylistUrl); + src.setBandwidth(playlist.getStreamInfo().getBandwidth()); + src.setHeight(Optional.ofNullable(playlist.getStreamInfo().getResolution()).map(r -> r.height).orElse(0)); + src.setMediaPlaylistUrl(playlist.getUri()); + log.trace("Media playlist {}", src.getMediaPlaylistUrl()); sources.add(src); } } @@ -100,7 +89,7 @@ public class XloveCamModel extends AbstractModel { try (Response response = getSite().getHttpClient().execute(req)) { if (response.isSuccessful()) { String body = response.body().string(); - LOG.trace(body); + log.trace(body); InputStream inputStream = new ByteArrayInputStream(body.getBytes(UTF_8)); PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT); Playlist playlist = parser.parse(); @@ -142,7 +131,7 @@ public class XloveCamModel extends AbstractModel { @Override public int[] getStreamResolution(boolean failFast) throws ExecutionException { - return new int[] {0, 0}; + return new int[]{0, 0}; } @Override