Added possibility to select the stream quality.
The Settings tab now contains a checkbox to enable manual stream selection. If not checked, the stream with the highest quality is selected.
This commit is contained in:
parent
48d2fad306
commit
98cefacae3
|
@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory;
|
|||
import ctbrec.ui.CookieJarImpl;
|
||||
import ctbrec.ui.HtmlParser;
|
||||
import ctbrec.ui.Launcher;
|
||||
import okhttp3.ConnectionPool;
|
||||
import okhttp3.Cookie;
|
||||
import okhttp3.FormBody;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
@ -32,7 +33,8 @@ public class HttpClient {
|
|||
.cookieJar(cookieJar)
|
||||
.connectTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.SECONDS)
|
||||
.readTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.SECONDS)
|
||||
.addInterceptor(new LoggingInterceptor())
|
||||
.connectionPool(new ConnectionPool(50, 10, TimeUnit.MINUTES))
|
||||
//.addInterceptor(new LoggingInterceptor())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ public class Model {
|
|||
private String description;
|
||||
private List<String> tags = new ArrayList<>();
|
||||
private boolean online = false;
|
||||
private int streamUrlIndex = -1;
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
|
@ -59,6 +60,14 @@ public class Model {
|
|||
this.description = description;
|
||||
}
|
||||
|
||||
public int getStreamUrlIndex() {
|
||||
return streamUrlIndex;
|
||||
}
|
||||
|
||||
public void setStreamUrlIndex(int streamUrlIndex) {
|
||||
this.streamUrlIndex = streamUrlIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
|
|
|
@ -19,5 +19,6 @@ public class Settings {
|
|||
public boolean automergeKeepSegments = false;
|
||||
public boolean determineResolution = false;
|
||||
public boolean requireAuthentication = false;
|
||||
public boolean chooseStreamQuality = false;
|
||||
public byte[] key = null;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package ctbrec.recorder;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -61,11 +60,7 @@ public class Chaturbate {
|
|||
return res;
|
||||
}
|
||||
|
||||
URL masterUrl = new URL(streamInfo.url);
|
||||
InputStream inputStream = masterUrl.openStream();
|
||||
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8);
|
||||
Playlist playlist = parser.parse();
|
||||
MasterPlaylist master = playlist.getMasterPlaylist();
|
||||
MasterPlaylist master = getMasterPlaylist(model, client);
|
||||
for (PlaylistData playlistData : master.getPlaylists()) {
|
||||
if(playlistData.hasStreamInfo() && playlistData.getStreamInfo().hasResolution()) {
|
||||
int h = playlistData.getStreamInfo().getResolution().height;
|
||||
|
@ -78,4 +73,20 @@ public class Chaturbate {
|
|||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public static MasterPlaylist getMasterPlaylist(Model model, HttpClient client) throws IOException, ParseException, PlaylistException {
|
||||
StreamInfo streamInfo = getStreamInfo(model, client);
|
||||
return getMasterPlaylist(streamInfo, client);
|
||||
}
|
||||
|
||||
public static MasterPlaylist getMasterPlaylist(StreamInfo streamInfo, HttpClient client) throws IOException, ParseException, PlaylistException {
|
||||
LOG.trace("Loading master playlist {}", streamInfo.url);
|
||||
Request req = new Request.Builder().url(streamInfo.url).build();
|
||||
Response response = client.execute(req);
|
||||
InputStream inputStream = response.body().byteStream();
|
||||
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8);
|
||||
Playlist playlist = parser.parse();
|
||||
MasterPlaylist master = playlist.getMasterPlaylist();
|
||||
return master;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ public class HlsDownload implements Download {
|
|||
Files.createDirectories(downloadDir);
|
||||
}
|
||||
|
||||
String segments = parseMaster(streamInfo.url);
|
||||
String segments = parseMaster(streamInfo.url, model.getStreamUrlIndex());
|
||||
if(segments != null) {
|
||||
int lastSegment = 0;
|
||||
int nextSegment = 0;
|
||||
|
@ -167,14 +167,19 @@ public class HlsDownload implements Download {
|
|||
return null;
|
||||
}
|
||||
|
||||
private String parseMaster(String url) throws IOException, ParseException, PlaylistException {
|
||||
private String parseMaster(String url, int streamUrlIndex) throws IOException, ParseException, PlaylistException {
|
||||
URL masterUrl = new URL(url);
|
||||
InputStream inputStream = masterUrl.openStream();
|
||||
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8);
|
||||
Playlist playlist = parser.parse();
|
||||
if(playlist.hasMasterPlaylist()) {
|
||||
MasterPlaylist master = playlist.getMasterPlaylist();
|
||||
PlaylistData bestQuality = master.getPlaylists().get(master.getPlaylists().size()-1);
|
||||
PlaylistData bestQuality = null;
|
||||
if(streamUrlIndex >= 0 && streamUrlIndex < master.getPlaylists().size()) {
|
||||
bestQuality = master.getPlaylists().get(streamUrlIndex);
|
||||
} else {
|
||||
bestQuality = master.getPlaylists().get(master.getPlaylists().size()-1);
|
||||
}
|
||||
String uri = bestQuality.getUri();
|
||||
if(!uri.startsWith("http")) {
|
||||
String _masterUrl = masterUrl.toString();
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package ctbrec.recorder.download;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
public class StreamSource {
|
||||
public int bandwidth;
|
||||
public int height;
|
||||
public String mediaPlaylistUrl;
|
||||
|
||||
public int getBandwidth() {
|
||||
return bandwidth;
|
||||
}
|
||||
|
||||
public void setBandwidth(int bandwidth) {
|
||||
this.bandwidth = bandwidth;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
DecimalFormat df = new DecimalFormat("0.00");
|
||||
float mbit = bandwidth / 1024.0f / 1024.0f;
|
||||
return height + "p (" + df.format(mbit) + " Mbit/s)";
|
||||
}
|
||||
}
|
|
@ -50,6 +50,7 @@ public class SettingsTab extends Tab {
|
|||
private CheckBox secureCommunication = new CheckBox();
|
||||
private CheckBox automerge = new CheckBox();
|
||||
private CheckBox automergeKeepSegments = new CheckBox();
|
||||
private CheckBox chooseStreamQuality = new CheckBox();
|
||||
private PasswordField password;
|
||||
private RadioButton recordLocal;
|
||||
private RadioButton recordRemote;
|
||||
|
@ -124,6 +125,14 @@ public class SettingsTab extends Tab {
|
|||
GridPane.setMargin(loadResolution, new Insets(CHECKBOX_MARGIN, 0, 0, 0));
|
||||
layout.add(loadResolution, 1, row);
|
||||
|
||||
l = new Label("Manually select stream quality");
|
||||
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, 0));
|
||||
layout.add(chooseStreamQuality, 1, row);
|
||||
|
||||
l = new Label("Auto-merge recordings");
|
||||
layout.add(l, 0, ++row);
|
||||
automerge.setSelected(Config.getInstance().getSettings().automerge);
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
package ctbrec.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.iheartradio.m3u8.ParseException;
|
||||
import com.iheartradio.m3u8.PlaylistException;
|
||||
import com.iheartradio.m3u8.data.MasterPlaylist;
|
||||
import com.iheartradio.m3u8.data.PlaylistData;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.HttpClient;
|
||||
|
@ -17,6 +23,7 @@ import ctbrec.Model;
|
|||
import ctbrec.recorder.Chaturbate;
|
||||
import ctbrec.recorder.Recorder;
|
||||
import ctbrec.recorder.StreamInfo;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.animation.FillTransition;
|
||||
import javafx.animation.Interpolator;
|
||||
|
@ -26,12 +33,14 @@ import javafx.application.Platform;
|
|||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ChoiceDialog;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.image.Image;
|
||||
|
@ -334,16 +343,76 @@ public class ThumbCell extends StackPane {
|
|||
|
||||
private void startStopAction(boolean start) {
|
||||
setCursor(Cursor.WAIT);
|
||||
|
||||
boolean selectSource = Config.getInstance().getSettings().chooseStreamQuality;
|
||||
if(selectSource && start) {
|
||||
Task<List<StreamSource>> selectStreamSource = new Task<List<StreamSource>>() {
|
||||
@Override
|
||||
protected List<StreamSource> call() throws Exception {
|
||||
StreamInfo streamInfo = Chaturbate.getStreamInfo(model, client);
|
||||
MasterPlaylist masterPlaylist = Chaturbate.getMasterPlaylist(streamInfo, client);
|
||||
List<StreamSource> sources = new ArrayList<>();
|
||||
for (PlaylistData playlist : masterPlaylist.getPlaylists()) {
|
||||
if (playlist.hasStreamInfo()) {
|
||||
StreamSource src = new StreamSource();
|
||||
src.bandwidth = playlist.getStreamInfo().getBandwidth();
|
||||
src.height = playlist.getStreamInfo().getResolution().height;
|
||||
String masterUrl = streamInfo.url;
|
||||
String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1);
|
||||
String segmentUri = baseUrl + playlist.getUri();
|
||||
src.mediaPlaylistUrl = segmentUri;
|
||||
LOG.trace("Media playlist {}", src.mediaPlaylistUrl);
|
||||
sources.add(src);
|
||||
}
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
};
|
||||
selectStreamSource.setOnSucceeded((e) -> {
|
||||
List<StreamSource> sources;
|
||||
try {
|
||||
sources = selectStreamSource.get();
|
||||
ChoiceDialog<StreamSource> choiceDialog = new ChoiceDialog<StreamSource>(sources.get(sources.size()-1), sources);
|
||||
choiceDialog.setTitle("Stream Quality");
|
||||
choiceDialog.setHeaderText("Select your preferred stream quality");
|
||||
Optional<StreamSource> selectedSource = choiceDialog.showAndWait();
|
||||
if(selectedSource.isPresent()) {
|
||||
int index = sources.indexOf(selectedSource.get());
|
||||
model.setStreamUrlIndex(index);
|
||||
_startStopAction(model, start);
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException e1) {
|
||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Error");
|
||||
alert.setHeaderText("Couldn't start/stop recording");
|
||||
alert.setContentText("I/O error while starting/stopping the recording: " + e1.getLocalizedMessage());
|
||||
alert.showAndWait();
|
||||
}
|
||||
});
|
||||
selectStreamSource.setOnFailed((e) -> {
|
||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Error");
|
||||
alert.setHeaderText("Couldn't start/stop recording");
|
||||
alert.setContentText("I/O error while starting/stopping the recording: " + selectStreamSource.getException().getLocalizedMessage());
|
||||
alert.showAndWait();
|
||||
});
|
||||
new Thread(selectStreamSource).start();
|
||||
} else {
|
||||
_startStopAction(model, start);
|
||||
}
|
||||
}
|
||||
|
||||
private void _startStopAction(Model model, boolean start) {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if(start) {
|
||||
// start the recording
|
||||
recorder.startRecording(model);
|
||||
} else {
|
||||
recorder.stopRecording(model);
|
||||
}
|
||||
setRecording(start);
|
||||
} catch (Exception e1) {
|
||||
LOG.error("Couldn't start/stop recording", e1);
|
||||
Platform.runLater(() -> {
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
</appender>
|
||||
-->
|
||||
|
||||
<root level="debug">
|
||||
<root level="trace">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="FILE" />
|
||||
<!--
|
||||
|
@ -38,7 +38,9 @@
|
|||
</root>
|
||||
|
||||
<logger name="ctbrec.LoggingInterceptor" level="INFO"/>
|
||||
<!--
|
||||
<logger name="ctbrec.recorder.Chaturbate" level="INFO" />
|
||||
-->
|
||||
<logger name="ctbrec.recorder.server.HlsServlet" level="INFO"/>
|
||||
<logger name="ctbrec.recorder.server.RecorderServlet" level="INFO"/>
|
||||
<logger name="ctbrec.ui.CookieJarImpl" level="INFO"/>
|
||||
|
|
Loading…
Reference in New Issue