diff --git a/src/main/java/ctbrec/Settings.java b/src/main/java/ctbrec/Settings.java index db2cbf0c..a8809ddf 100644 --- a/src/main/java/ctbrec/Settings.java +++ b/src/main/java/ctbrec/Settings.java @@ -34,6 +34,7 @@ public class Settings { public boolean determineResolution = false; public boolean requireAuthentication = false; public boolean chooseStreamQuality = false; + public int maximumResolution = 0; public byte[] key = null; public ProxyType proxyType = ProxyType.DIRECT; public String proxyHost; diff --git a/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java b/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java index 0bf3d61e..97bc1bdd 100644 --- a/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java +++ b/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java @@ -7,11 +7,15 @@ import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.iheartradio.m3u8.Encoding; import com.iheartradio.m3u8.Format; import com.iheartradio.m3u8.ParseException; @@ -22,6 +26,7 @@ import com.iheartradio.m3u8.data.MediaPlaylist; import com.iheartradio.m3u8.data.Playlist; import com.iheartradio.m3u8.data.TrackData; +import ctbrec.Config; import ctbrec.Model; import ctbrec.io.HttpClient; import okhttp3.Request; @@ -29,6 +34,8 @@ import okhttp3.Response; public abstract class AbstractHlsDownload implements Download { + private static final transient Logger LOG = LoggerFactory.getLogger(AbstractHlsDownload.class); + ExecutorService downloadThreadPool = Executors.newFixedThreadPool(5); HttpClient client; volatile boolean running = false; @@ -80,7 +87,22 @@ public abstract class AbstractHlsDownload implements Download { url = streamSources.get(model.getStreamUrlIndex()).getMediaPlaylistUrl(); } else { Collections.sort(streamSources); - url = streamSources.get(streamSources.size()-1).getMediaPlaylistUrl(); + // filter out stream resolutions, which are too high + int maxRes = Config.getInstance().getSettings().maximumResolution; + if(maxRes > 0) { + for (Iterator iterator = streamSources.iterator(); iterator.hasNext();) { + StreamSource streamSource = iterator.next(); + if(streamSource.height > 0 && maxRes < streamSource.height) { + LOG.debug("Res too high {} > {}", streamSource.height, maxRes); + iterator.remove(); + } + } + } + if(streamSources.isEmpty()) { + throw new ExecutionException(new RuntimeException("No stream left in playlist")); + } else { + url = streamSources.get(streamSources.size()-1).getMediaPlaylistUrl(); + } } return url; } diff --git a/src/main/java/ctbrec/ui/SettingsTab.java b/src/main/java/ctbrec/ui/SettingsTab.java index 693427d6..f0066613 100644 --- a/src/main/java/ctbrec/ui/SettingsTab.java +++ b/src/main/java/ctbrec/ui/SettingsTab.java @@ -65,6 +65,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { private RadioButton recordRemote; private ToggleGroup recordLocation; private ProxySettingsPane proxySettingsPane; + private ComboBox maxResolution; private ComboBox splitAfter; private List sites; private Label restartLabel; @@ -231,8 +232,8 @@ public class SettingsTab extends Tab implements TabSelectionListener { keyDialog.show(); } }); - GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, CHECKBOX_MARGIN, 0, 0)); - GridPane.setMargin(secureCommunication, new Insets(CHECKBOX_MARGIN, 0, 0, 0)); + GridPane.setMargin(l, new Insets(4, CHECKBOX_MARGIN, 0, 0)); + GridPane.setMargin(secureCommunication, new Insets(4, 0, 0, 0)); layout.add(secureCommunication, 1, 3); TitledPane recordLocation = new TitledPane("Record Location", layout); @@ -249,6 +250,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { GridPane.setFillWidth(recordingsDirectory, true); GridPane.setHgrow(recordingsDirectory, Priority.ALWAYS); GridPane.setColumnSpan(recordingsDirectory, 2); + GridPane.setMargin(recordingsDirectory, new Insets(0, 0, 0, CHECKBOX_MARGIN)); layout.add(recordingsDirectory, 1, 0); recordingsDirectoryButton = createRecordingsBrowseButton(); layout.add(recordingsDirectoryButton, 3, 0); @@ -259,19 +261,10 @@ public class SettingsTab extends Tab implements TabSelectionListener { GridPane.setFillWidth(mediaPlayer, true); GridPane.setHgrow(mediaPlayer, Priority.ALWAYS); GridPane.setColumnSpan(mediaPlayer, 2); + GridPane.setMargin(mediaPlayer, new Insets(0, 0, 0, CHECKBOX_MARGIN)); layout.add(mediaPlayer, 1, 1); layout.add(createMpvBrowseButton(), 3, 1); - Label l = new Label("Allow multiple players"); - layout.add(l, 0, 2); - multiplePlayers.setSelected(!Config.getInstance().getSettings().singlePlayer); - multiplePlayers.setOnAction((e) -> Config.getInstance().getSettings().singlePlayer = !multiplePlayers.isSelected()); - GridPane.setMargin(recordingsDirectory, new Insets(0, 0, 0, CHECKBOX_MARGIN)); - GridPane.setMargin(mediaPlayer, new Insets(0, 0, 0, CHECKBOX_MARGIN)); - GridPane.setMargin(l, new Insets(3, 0, 0, 0)); - GridPane.setMargin(multiplePlayers, new Insets(3, 0, 0, CHECKBOX_MARGIN)); - layout.add(multiplePlayers, 1, 2); - TitledPane locations = new TitledPane("Locations", layout); locations.setCollapsible(false); return locations; @@ -279,8 +272,9 @@ public class SettingsTab extends Tab implements TabSelectionListener { private Node createGeneralPanel() { GridPane layout = createGridLayout(); + int row = 0; Label l = new Label("Display stream resolution in overview"); - layout.add(l, 0, 0); + layout.add(l, 0, row); loadResolution = new CheckBox(); loadResolution.setSelected(Config.getInstance().getSettings().determineResolution); loadResolution.setOnAction((e) -> { @@ -291,18 +285,41 @@ public class SettingsTab extends Tab implements TabSelectionListener { }); //GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0)); GridPane.setMargin(loadResolution, new Insets(0, 0, 0, CHECKBOX_MARGIN)); - layout.add(loadResolution, 1, 0); + layout.add(loadResolution, 1, row++); + + l = new Label("Allow multiple players"); + layout.add(l, 0, row); + multiplePlayers.setSelected(!Config.getInstance().getSettings().singlePlayer); + multiplePlayers.setOnAction((e) -> Config.getInstance().getSettings().singlePlayer = !multiplePlayers.isSelected()); + GridPane.setMargin(l, new Insets(3, 0, 0, 0)); + GridPane.setMargin(multiplePlayers, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN)); + layout.add(multiplePlayers, 1, row++); l = new Label("Manually select stream quality"); - layout.add(l, 0, 1); + layout.add(l, 0, row); chooseStreamQuality.setSelected(Config.getInstance().getSettings().chooseStreamQuality); chooseStreamQuality.setOnAction((e) -> Config.getInstance().getSettings().chooseStreamQuality = chooseStreamQuality.isSelected()); GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0)); GridPane.setMargin(chooseStreamQuality, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN)); - layout.add(chooseStreamQuality, 1, 1); + layout.add(chooseStreamQuality, 1, row++); + + l = new Label("Maximum resolution (0 = unlimited)"); + layout.add(l, 0, row); + List resolutionOptions = new ArrayList<>(); + resolutionOptions.add(1080); + resolutionOptions.add(720); + resolutionOptions.add(600); + resolutionOptions.add(480); + resolutionOptions.add(0); + maxResolution = new ComboBox<>(new ObservableListWrapper<>(resolutionOptions)); + setMaxResolutionValue(); + maxResolution.setOnAction((e) -> Config.getInstance().getSettings().maximumResolution = maxResolution.getSelectionModel().getSelectedItem()); + layout.add(maxResolution, 1, row++); + GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0)); + GridPane.setMargin(maxResolution, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN)); l = new Label("Split recordings after (minutes)"); - layout.add(l, 0, 2); + layout.add(l, 0, row); List options = new ArrayList<>(); options.add(new SplitAfterOption("disabled", 0)); options.add(new SplitAfterOption("10 min", 10 * 60)); @@ -311,11 +328,12 @@ public class SettingsTab extends Tab implements TabSelectionListener { options.add(new SplitAfterOption("30 min", 30 * 60)); options.add(new SplitAfterOption("60 min", 60 * 60)); splitAfter = new ComboBox<>(new ObservableListWrapper<>(options)); - layout.add(splitAfter, 1, 2); + layout.add(splitAfter, 1, row++); setSplitAfterValue(); splitAfter.setOnAction((e) -> Config.getInstance().getSettings().splitRecordings = splitAfter.getSelectionModel().getSelectedItem().getValue()); - GridPane.setMargin(l, new Insets(CHECKBOX_MARGIN, 0, 0, 0)); - GridPane.setMargin(splitAfter, new Insets(CHECKBOX_MARGIN, 0, 0, CHECKBOX_MARGIN)); + GridPane.setMargin(l, new Insets(0, 0, 0, 0)); + GridPane.setMargin(splitAfter, new Insets(0, 0, 0, CHECKBOX_MARGIN)); + maxResolution.prefWidthProperty().bind(splitAfter.widthProperty()); TitledPane general = new TitledPane("General", layout); general.setCollapsible(false); @@ -331,6 +349,15 @@ public class SettingsTab extends Tab implements TabSelectionListener { } } + private void setMaxResolutionValue() { + int value = Config.getInstance().getSettings().maximumResolution; + for (Integer option : maxResolution.getItems()) { + if(option == value) { + maxResolution.getSelectionModel().select(option); + } + } + } + void showRestartRequired() { restartLabel.setVisible(true); } @@ -350,6 +377,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { recordingsDirectory.setDisable(!local); recordingsDirectoryButton.setDisable(!local); splitAfter.setDisable(!local); + maxResolution.setDisable(!local); } private ChangeListener createRecordingsDirectoryFocusListener() {