diff --git a/client/src/main/java/ctbrec/ui/Player.java b/client/src/main/java/ctbrec/ui/Player.java index 5af1cb7f..96df79dd 100644 --- a/client/src/main/java/ctbrec/ui/Player.java +++ b/client/src/main/java/ctbrec/ui/Player.java @@ -1,6 +1,7 @@ package ctbrec.ui; import java.io.File; +import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; @@ -9,11 +10,18 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.concurrent.ExecutionException; + +import javax.xml.bind.JAXBException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.iheartradio.m3u8.ParseException; +import com.iheartradio.m3u8.PlaylistException; + import ctbrec.Config; import ctbrec.Hmac; import ctbrec.Model; @@ -32,11 +40,7 @@ public class Player { private Player() { } - public static boolean play(String url) { - return play(url, true); - } - - public static boolean play(String url, boolean async) { + private static boolean play(String url, boolean async) { boolean singlePlayer = Config.getInstance().getSettings().singlePlayer; try { if (singlePlayer && playerThread != null && playerThread.isRunning()) { @@ -80,11 +84,9 @@ public class Player { if (singlePlayer && playerThread != null && playerThread.isRunning()) { playerThread.stopThread(); } - List sources = model.getStreamSources(); - Collections.sort(sources); - StreamSource best = sources.get(sources.size() - 1); - LOG.debug("Playing {}", best.getMediaPlaylistUrl()); - return Player.play(best.getMediaPlaylistUrl(), async); + String playlistUrl = getPlaylistUrl(model); + LOG.debug("Playing {}", playlistUrl); + return Player.play(playlistUrl, async); } else { Dialogs.showError(scene, "Room not public", "Room is currently not public", null); return false; @@ -96,6 +98,29 @@ public class Player { } } + private static String getPlaylistUrl(Model model) throws IOException, ExecutionException, ParseException, PlaylistException, JAXBException { + List sources = model.getStreamSources(); + Collections.sort(sources); + StreamSource best; + int maxRes = Config.getInstance().getSettings().maximumResolutionPlayer; + 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); + iterator.remove(); + } + } + } + if (sources.isEmpty()) { + throw new RuntimeException("No stream left in playlist, because player resolution is set to " + maxRes); + } else { + LOG.debug("{} selected {}", model.getName(), sources.get(sources.size() - 1)); + best = sources.get(sources.size() - 1); + } + return best.getMediaPlaylistUrl(); + } + public static void stop() { if (playerThread != null) { playerThread.stopThread(); diff --git a/client/src/main/java/ctbrec/ui/settings/PlayerSettingsDialog.java b/client/src/main/java/ctbrec/ui/settings/PlayerSettingsDialog.java new file mode 100644 index 00000000..225d85cf --- /dev/null +++ b/client/src/main/java/ctbrec/ui/settings/PlayerSettingsDialog.java @@ -0,0 +1,101 @@ +package ctbrec.ui.settings; + +import java.io.IOException; +import java.io.InputStream; + +import ctbrec.Config; +import ctbrec.Settings; +import ctbrec.ui.controls.Dialogs; +import javafx.application.Platform; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; +import javafx.scene.image.Image; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; +import javafx.stage.Modality; +import javafx.stage.Stage; + +public class PlayerSettingsDialog extends Dialog { + + private Scene parent; + private Config config; + private Settings settings; + + private TextField playerParams; + private TextField maxResolution; + + public PlayerSettingsDialog(Scene parent, Config config) { + this.parent = parent; + this.config = config; + this.settings = config.getSettings(); + + initGui(); + } + + private void initGui() { + setTitle("Player Settings"); + setHeaderText("Player Settings"); + getDialogPane().getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL); + initModality(Modality.APPLICATION_MODAL); + setResizable(true); + InputStream icon = Dialogs.class.getResourceAsStream("/icon.png"); + Stage stage = (Stage) getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image(icon)); + if (parent != null) { + stage.getScene().getStylesheets().addAll(parent.getStylesheets()); + } + + GridPane grid = new GridPane(); + grid.setHgap(10); + grid.setVgap(10); + grid.setPadding(new Insets(20, 150, 10, 10)); + + grid.add(new Label("Start parameters"), 0, 0); + playerParams = new TextField(settings.mediaPlayerParams); + grid.add(playerParams, 1, 0); + getDialogPane().setContent(grid); + GridPane.setFillWidth(playerParams, true); + GridPane.setHgrow(playerParams, Priority.ALWAYS); + + Label l = new Label("Maximum resolution (0 = unlimited)"); + grid.add(l, 0, 1); + maxResolution = new TextField(Integer.toString(settings.maximumResolutionPlayer)); + Tooltip tt = new Tooltip("video height, e.g. 720 or 1080"); + l.setTooltip(tt); + maxResolution.setTooltip(tt); + grid.add(maxResolution, 1, 1); + getDialogPane().setContent(grid); + GridPane.setFillWidth(maxResolution, true); + GridPane.setHgrow(maxResolution, Priority.ALWAYS); + + Platform.runLater(playerParams::requestFocus); + + setResultConverter(dialogButton -> { + try { + if (dialogButton == ButtonType.OK) { + saveSettings(); + } + return null; + } catch (IOException e) { + return e; + } + }); + } + + public void saveSettings() throws IOException { + settings.mediaPlayerParams = playerParams.getText(); + String res = maxResolution.getText(); + if (res.matches("\\d+")) { + int newRes = Integer.parseInt(maxResolution.getText()); + if (newRes != Config.getInstance().getSettings().maximumResolutionPlayer) { + settings.maximumResolutionPlayer = newRes; + } + } + config.save(); + } +} diff --git a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java index 9fa5e77e..167d4fbc 100644 --- a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java +++ b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java @@ -67,7 +67,7 @@ import javafx.stage.FileChooser; public class SettingsTab extends Tab implements TabSelectionListener { - private static final String PATTERN_NOT_A_DIGIT = "[^\\d]"; + public static final String PATTERN_NOT_A_DIGIT = "[^\\d]"; private static final Logger LOG = LoggerFactory.getLogger(SettingsTab.class); private static final int ONE_GIB_IN_BYTES = 1024 * 1024 * 1024; @@ -626,12 +626,17 @@ public class SettingsTab extends Tab implements TabSelectionListener { layout.add(mediaPlayer, 1, row); Button mediaPlayerParamsButton = new Button("⚙"); mediaPlayerParamsButton.setOnAction(e -> { - Optional playerParams = Dialogs.showTextInput(mediaPlayerParamsButton.getScene(), "Media Player Parameters", - "Media Player Start Parameters", settings.mediaPlayerParams); - playerParams.ifPresent(p -> { - settings.mediaPlayerParams = p; - saveConfig(); - }); + // Optional playerParams = Dialogs.showTextInput(mediaPlayerParamsButton.getScene(), "Media Player Parameters", + // "Media Player Start Parameters", settings.mediaPlayerParams); + // playerParams.ifPresent(p -> { + // settings.mediaPlayerParams = p; + // saveConfig(); + // }); + PlayerSettingsDialog dialog = new PlayerSettingsDialog(getTabPane().getScene(), Config.getInstance()); + Optional exception = dialog.showAndWait(); + if (exception.isPresent()) { + Dialogs.showError("Saving player parameters", "Player parameters couldn't be saved", exception.get()); + } }); layout.add(mediaPlayerParamsButton, 3, row++); diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java index a392ae66..e8e30442 100644 --- a/common/src/main/java/ctbrec/Settings.java +++ b/common/src/main/java/ctbrec/Settings.java @@ -70,6 +70,7 @@ public class Settings { public boolean livePreviews = false; public boolean localRecording = true; public int maximumResolution = 0; + public int maximumResolutionPlayer = 0; public String mediaPlayer = "/usr/bin/mpv"; public String mediaPlayerParams = ""; public String mfcBaseUrl = "https://www.myfreecams.com"; diff --git a/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java b/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java index f63dbf66..a196a5ad 100644 --- a/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java +++ b/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java @@ -5,6 +5,7 @@ import static ctbrec.io.HttpConstants.*; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -44,7 +45,7 @@ public class CamsodaModel extends AbstractModel { private static final String STATUS = "status"; private static final Logger LOG = LoggerFactory.getLogger(CamsodaModel.class); private String streamUrl; - private List streamSources = null; + private transient List streamSources = null; private float sortOrder = 0; private Random random = new Random(); int[] resolution = new int[2]; @@ -126,7 +127,8 @@ public class CamsodaModel extends AbstractModel { streamsource.width = 0; streamsource.height = 0; } - streamSources = Collections.singletonList(streamsource); + streamSources = new ArrayList<>(); + streamSources.add(streamsource); } else { LOG.trace("Response: {}", response.body().string()); throw new HttpException(response.code(), response.message()); diff --git a/common/src/main/java/ctbrec/sites/showup/ShowupHttpClient.java b/common/src/main/java/ctbrec/sites/showup/ShowupHttpClient.java index c99b5a8f..be0eb313 100644 --- a/common/src/main/java/ctbrec/sites/showup/ShowupHttpClient.java +++ b/common/src/main/java/ctbrec/sites/showup/ShowupHttpClient.java @@ -104,8 +104,6 @@ public class ShowupHttpClient extends HttpClient { JSONObject json = new JSONObject(responseBody); return json.optString("status").equalsIgnoreCase("success"); } else { - String msg = "Login was not successful"; - LOG.warn("{}\n{}", msg, responseBody); return false; } } else { diff --git a/common/src/main/java/ctbrec/sites/showup/ShowupModel.java b/common/src/main/java/ctbrec/sites/showup/ShowupModel.java index cef8ac33..f76e4a5c 100644 --- a/common/src/main/java/ctbrec/sites/showup/ShowupModel.java +++ b/common/src/main/java/ctbrec/sites/showup/ShowupModel.java @@ -4,7 +4,7 @@ import static ctbrec.Model.State.*; import java.io.IOException; import java.text.MessageFormat; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.ExecutionException; @@ -70,7 +70,9 @@ public class ShowupModel extends AbstractModel { int cdnHost = 1 + new Random().nextInt(5); src.mediaPlaylistUrl = MessageFormat.format("https://cdn-e0{0}.showup.tv/h5live/http/playlist.m3u8?url=rtmp%3A%2F%2F{1}%3A1935%2Fwebrtc&stream={2}_aac", cdnHost, streamTranscoderAddr, streamId); - return Collections.singletonList(src); + List sources = new ArrayList<>(); + sources.add(src); + return sources; } @Override