Fix Flirt4Free recordings
This commit is contained in:
parent
5cf45a87e3
commit
062a450e69
|
@ -14,8 +14,6 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
@ -74,13 +72,13 @@ public class Flirt4FreeUpdateService extends PaginatedScheduledService {
|
||||||
try {
|
try {
|
||||||
Flirt4FreeModel model = parseModel(modelJson);
|
Flirt4FreeModel model = parseModel(modelJson);
|
||||||
models.add(model);
|
models.add(model);
|
||||||
} catch(Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.warn("Couldn't parse model {}", modelJson);
|
LOG.warn("Couldn't parse model {}", modelJson);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return models.stream()
|
return models.stream()
|
||||||
.filter(filter)
|
.filter(filter)
|
||||||
.skip((page - 1) * (long)MODELS_PER_PAGE)
|
.skip((page - 1) * (long) MODELS_PER_PAGE)
|
||||||
.limit(MODELS_PER_PAGE)
|
.limit(MODELS_PER_PAGE)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
} else {
|
} else {
|
||||||
|
@ -112,6 +110,14 @@ public class Flirt4FreeUpdateService extends PaginatedScheduledService {
|
||||||
if (modelData.has("category_id_3")) {
|
if (modelData.has("category_id_3")) {
|
||||||
model.getCategories().add(modelData.getString("category_id_3"));
|
model.getCategories().add(modelData.getString("category_id_3"));
|
||||||
}
|
}
|
||||||
|
if (modelData.has("video_width") && modelData.has("video_aspect_ratio")) {
|
||||||
|
String[] aspectRatioParts = modelData.getString("video_aspect_ratio").split(":");
|
||||||
|
double aspectWidth = Integer.parseInt(aspectRatioParts[0]);
|
||||||
|
double aspectHeight = Integer.parseInt(aspectRatioParts[1]);
|
||||||
|
int width = Integer.parseInt(modelData.getString("video_width"));
|
||||||
|
int height = (int) Math.ceil(width * aspectHeight / aspectWidth);
|
||||||
|
model.setStreamResolution(new int[]{width, height});
|
||||||
|
}
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package ctbrec;
|
||||||
|
|
||||||
|
public class StringConstants {
|
||||||
|
|
||||||
|
public static final String MODEL_ID = "model_id";
|
||||||
|
public static final String STATUS = "status";
|
||||||
|
|
||||||
|
private StringConstants() {
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,21 @@
|
||||||
package ctbrec.sites.flirt4free;
|
package ctbrec.sites.flirt4free;
|
||||||
|
|
||||||
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 com.squareup.moshi.JsonReader;
|
||||||
|
import com.squareup.moshi.JsonWriter;
|
||||||
|
import ctbrec.AbstractModel;
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.io.HttpException;
|
||||||
|
import ctbrec.recorder.download.StreamSource;
|
||||||
|
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.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -12,35 +27,15 @@ import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.json.JSONObject;
|
import static ctbrec.StringConstants.MODEL_ID;
|
||||||
import org.slf4j.Logger;
|
import static ctbrec.StringConstants.STATUS;
|
||||||
import org.slf4j.LoggerFactory;
|
import static ctbrec.io.HttpConstants.*;
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import com.iheartradio.m3u8.Encoding;
|
import static java.util.Locale.ENGLISH;
|
||||||
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 com.squareup.moshi.JsonReader;
|
|
||||||
import com.squareup.moshi.JsonWriter;
|
|
||||||
|
|
||||||
import ctbrec.AbstractModel;
|
|
||||||
import ctbrec.Config;
|
|
||||||
import ctbrec.Model;
|
|
||||||
import ctbrec.io.HttpException;
|
|
||||||
import ctbrec.recorder.download.StreamSource;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import okhttp3.WebSocket;
|
|
||||||
import okhttp3.WebSocketListener;
|
|
||||||
|
|
||||||
public class Flirt4FreeModel extends AbstractModel {
|
public class Flirt4FreeModel extends AbstractModel {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(Flirt4FreeModel.class);
|
private static final Logger LOG = LoggerFactory.getLogger(Flirt4FreeModel.class);
|
||||||
private String id;
|
private String id;
|
||||||
private String chatHost;
|
private String chatHost;
|
||||||
private String chatPort;
|
private String chatPort;
|
||||||
|
@ -72,7 +67,7 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.header(ACCEPT, "*/*")
|
.header(ACCEPT, "*/*")
|
||||||
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
|
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
|
||||||
.header(REFERER, getUrl())
|
.header(REFERER, getUrl())
|
||||||
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||||
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
||||||
|
@ -97,7 +92,7 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
JSONObject json = new JSONObject(body);
|
JSONObject json = new JSONObject(body);
|
||||||
online = Objects.equals(json.optString("status"), "online"); // online is true, even if the model is in private or away
|
online = Objects.equals(json.optString(STATUS), "online"); // online is true, even if the model is in private or away
|
||||||
updateModelId(json);
|
updateModelId(json);
|
||||||
if (online) {
|
if (online) {
|
||||||
try {
|
try {
|
||||||
|
@ -110,12 +105,11 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateModelId(JSONObject json) {
|
private void updateModelId(JSONObject json) {
|
||||||
if (json.has("model_id")) {
|
if (json.has(MODEL_ID)) {
|
||||||
Object modelId = json.get("model_id");
|
Object modelId = json.get(MODEL_ID);
|
||||||
if (modelId instanceof Number) {
|
if (modelId instanceof Number n) {
|
||||||
Number n = (Number) modelId;
|
|
||||||
if (n.intValue() > 0) {
|
if (n.intValue() > 0) {
|
||||||
id = String.valueOf(json.get("model_id"));
|
id = String.valueOf(json.get(MODEL_ID));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +121,7 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.header(ACCEPT, "*/*")
|
.header(ACCEPT, "*/*")
|
||||||
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
|
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
|
||||||
.header(REFERER, getUrl())
|
.header(REFERER, getUrl())
|
||||||
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||||
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
||||||
|
@ -135,7 +129,7 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
try (Response response = getSite().getHttpClient().execute(request)) {
|
try (Response response = getSite().getHttpClient().execute(request)) {
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
JSONObject json = new JSONObject(response.body().string());
|
JSONObject json = new JSONObject(response.body().string());
|
||||||
if (json.optString("status").equals("success")) {
|
if (json.optString(STATUS).equals("success")) {
|
||||||
JSONObject config = json.getJSONObject("config");
|
JSONObject config = json.getJSONObject("config");
|
||||||
JSONObject performer = config.getJSONObject("performer");
|
JSONObject performer = config.getJSONObject("performer");
|
||||||
setUrl(getSite().getBaseUrl() + "/rooms/" + getName() + '/');
|
setUrl(getSite().getBaseUrl() + "/rooms/" + getName() + '/');
|
||||||
|
@ -144,7 +138,7 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
chatHost = room.getString("host");
|
chatHost = room.getString("host");
|
||||||
chatPort = room.getString("port_to_be");
|
chatPort = room.getString("port_to_be");
|
||||||
chatToken = json.getString("token_enc");
|
chatToken = json.getString("token_enc");
|
||||||
String status = room.optString("status");
|
String status = room.optString(STATUS);
|
||||||
setOnlineState(mapStatus(status));
|
setOnlineState(mapStatus(status));
|
||||||
online = onlineState == State.ONLINE;
|
online = onlineState == State.ONLINE;
|
||||||
JSONObject user = config.getJSONObject("user");
|
JSONObject user = config.getJSONObject("user");
|
||||||
|
@ -161,16 +155,12 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private State mapStatus(String status) {
|
private State mapStatus(String status) {
|
||||||
switch (status) {
|
return switch (status) {
|
||||||
case "P":
|
case "P", "F" -> State.PRIVATE;
|
||||||
case "F":
|
case "A" -> State.AWAY;
|
||||||
return State.PRIVATE;
|
case "O" -> State.ONLINE;
|
||||||
case "A":
|
default -> State.UNKNOWN;
|
||||||
return State.AWAY;
|
};
|
||||||
case "O":
|
|
||||||
default:
|
|
||||||
return State.ONLINE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -179,7 +169,7 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<StreamSource> getStreamSources(boolean withWebsocket) throws IOException, ExecutionException, ParseException, PlaylistException {
|
private List<StreamSource> getStreamSources(boolean withWebsocket) throws IOException, ExecutionException, ParseException, PlaylistException {
|
||||||
MasterPlaylist masterPlaylist = null;
|
MasterPlaylist masterPlaylist;
|
||||||
try {
|
try {
|
||||||
if (withWebsocket) {
|
if (withWebsocket) {
|
||||||
acquireSlot();
|
acquireSlot();
|
||||||
|
@ -200,7 +190,8 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
StreamSource src = new StreamSource();
|
StreamSource src = new StreamSource();
|
||||||
src.bandwidth = playlist.getStreamInfo().getBandwidth();
|
src.bandwidth = playlist.getStreamInfo().getBandwidth();
|
||||||
src.height = playlist.getStreamInfo().getResolution().height;
|
src.height = playlist.getStreamInfo().getResolution().height;
|
||||||
src.mediaPlaylistUrl = "https://manifest.vscdns.com/" + playlist.getUri();
|
HttpUrl masterPlaylistUrl = HttpUrl.parse(streamUrl);
|
||||||
|
src.mediaPlaylistUrl = "https://" + masterPlaylistUrl.host() + '/' + playlist.getUri();
|
||||||
LOG.trace("Media playlist {}", src.mediaPlaylistUrl);
|
LOG.trace("Media playlist {}", src.mediaPlaylistUrl);
|
||||||
sources.add(src);
|
sources.add(src);
|
||||||
}
|
}
|
||||||
|
@ -213,7 +204,7 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
Request req = new Request.Builder()
|
Request req = new Request.Builder()
|
||||||
.url(streamUrl)
|
.url(streamUrl)
|
||||||
.header(ACCEPT, "*/*")
|
.header(ACCEPT, "*/*")
|
||||||
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
|
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
|
||||||
.header(REFERER, getUrl())
|
.header(REFERER, getUrl())
|
||||||
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||||
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
||||||
|
@ -238,12 +229,12 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
loadModelInfo();
|
loadModelInfo();
|
||||||
Objects.requireNonNull(chatHost, "chatHost is null");
|
Objects.requireNonNull(chatHost, "chatHost is null");
|
||||||
String h = chatHost.replace("chat", "chat-vip");
|
String h = chatHost.replace("chat", "chat-vip");
|
||||||
String url = "https://" + h + "/chat?token=" + URLEncoder.encode(chatToken, "utf-8") + "&port_to_be=" + chatPort;
|
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()
|
Request req = new Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.header(ACCEPT, "*/*")
|
.header(ACCEPT, "*/*")
|
||||||
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
|
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
|
||||||
.header(REFERER, getUrl())
|
.header(REFERER, getUrl())
|
||||||
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||||
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
||||||
|
@ -268,14 +259,14 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
String roomState = data.optString("room_state");
|
String roomState = data.optString("room_state");
|
||||||
onlineState = mapStatus(roomState);
|
onlineState = mapStatus(roomState);
|
||||||
online = onlineState == State.ONLINE;
|
online = onlineState == State.ONLINE;
|
||||||
if(data.optString("room_state").equals("0") && data.optString("login_group_id").equals("14")) {
|
if (data.optString("room_state").equals("0") && data.optString("login_group_id").equals("14")) {
|
||||||
onlineState = Model.State.GROUP;
|
onlineState = Model.State.GROUP;
|
||||||
online = false;
|
online = false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
resolution[0] = Integer.parseInt(data.getString("stream_width"));
|
resolution[0] = Integer.parseInt(data.getString("stream_width"));
|
||||||
resolution[1] = Integer.parseInt(data.getString("stream_height"));
|
resolution[1] = Integer.parseInt(data.getString("stream_height"));
|
||||||
} catch(Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.warn("Couldn't determine stream resolution", e);
|
LOG.warn("Couldn't determine stream resolution", e);
|
||||||
}
|
}
|
||||||
webSocket.close(1000, "");
|
webSocket.close(1000, "");
|
||||||
|
@ -288,8 +279,10 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
synchronized (monitor) {
|
synchronized (monitor) {
|
||||||
monitor.notifyAll();
|
monitor.notifyAll();
|
||||||
}
|
}
|
||||||
|
if (response != null) {
|
||||||
response.close();
|
response.close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||||
|
@ -305,11 +298,28 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
if (streamHost == null) {
|
if (streamHost == null) {
|
||||||
throw new RuntimeException("Couldn't determine streaming server for model " + getName());
|
throw new RuntimeException("Couldn't determine streaming server for model " + getName());
|
||||||
} else {
|
} else {
|
||||||
streamUrl = "https://manifest.vscdns.com/manifest.m3u8.m3u8?key=nil&provider=highwinds&secure=true&host=" + streamHost + "&model_id=" + id;
|
url = getSite().getBaseUrl() + "/ws/chat/get-stream-urls.php?"
|
||||||
|
+ "model_id=" + id
|
||||||
|
+ "&video_host=" + streamHost
|
||||||
|
+ "&t=" + System.currentTimeMillis();
|
||||||
|
LOG.debug("Loading master playlist information: {}", url);
|
||||||
|
req = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.header(ACCEPT, "*/*")
|
||||||
|
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
|
||||||
|
.header(REFERER, getUrl())
|
||||||
|
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||||
|
.header(REFERER, getUrl())
|
||||||
|
.build();
|
||||||
|
try (Response response = getSite().getHttpClient().execute(req)) {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateCacheEntries() {
|
public void invalidateCacheEntries() {
|
||||||
|
@ -339,7 +349,7 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
Request req = new Request.Builder()
|
Request req = new Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.header(ACCEPT, "*/*")
|
.header(ACCEPT, "*/*")
|
||||||
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
|
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
|
||||||
.header(REFERER, getUrl())
|
.header(REFERER, getUrl())
|
||||||
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||||
.header(REFERER, getUrl())
|
.header(REFERER, getUrl())
|
||||||
|
@ -412,21 +422,21 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int[] getStreamResolution(boolean failFast) throws ExecutionException {
|
public int[] getStreamResolution(boolean failFast) throws ExecutionException {
|
||||||
if(failFast) {
|
if (!failFast && streamUrl != null && resolution[0] == 0) {
|
||||||
return resolution;
|
|
||||||
} else {
|
|
||||||
if(streamUrl != null) {
|
|
||||||
try {
|
try {
|
||||||
List<StreamSource> streamSources = getStreamSources(false);
|
List<StreamSource> streamSources = getStreamSources(true);
|
||||||
Collections.sort(streamSources);
|
Collections.sort(streamSources);
|
||||||
StreamSource best = streamSources.get(streamSources.size()-1);
|
StreamSource best = streamSources.get(streamSources.size() - 1);
|
||||||
resolution = new int[] {best.width, best.height};
|
resolution = new int[]{best.width, best.height};
|
||||||
} catch (IOException | ParseException | PlaylistException e) {
|
} catch (IOException | ParseException | PlaylistException e) {
|
||||||
throw new ExecutionException("Couldn't determine stream resolution", e);
|
throw new ExecutionException("Couldn't determine stream resolution", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resolution;
|
return resolution;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setStreamResolution(int[] res) {
|
||||||
|
this.resolution = res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -521,7 +531,7 @@ public class Flirt4FreeModel extends AbstractModel {
|
||||||
requestThrottle.acquire();
|
requestThrottle.acquire();
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long millisSinceLastRequest = now - lastRequest;
|
long millisSinceLastRequest = now - lastRequest;
|
||||||
if(millisSinceLastRequest < 500) {
|
if (millisSinceLastRequest < 500) {
|
||||||
Thread.sleep(500 - millisSinceLastRequest);
|
Thread.sleep(500 - millisSinceLastRequest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue