Converted from Optional to ActionResult

Moved Logger creation to LoggerFactory
Fixed creating more than 1 recording thread for each livestream
And more optimizations!
This commit is contained in:
kohlerpop1
2024-02-19 14:55:59 -05:00
parent 6b22154c82
commit 1b2a8bad93
9 changed files with 150 additions and 206 deletions

View File

@@ -44,14 +44,12 @@ import io.github.jwdeveloper.tiktok.mappers.*;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import io.github.jwdeveloper.tiktok.mappers.handlers.*;
import io.github.jwdeveloper.tiktok.messages.webcast.*;
import io.github.jwdeveloper.tiktok.utils.ConsoleColors;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.logging.Formatter;
import java.util.logging.*;
import java.util.logging.Logger;
public class TikTokLiveClientBuilder implements LiveClientBuilder {
@@ -61,8 +59,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
protected Consumer<TikTokMapper> onCustomMappings;
protected Logger logger;
public TikTokLiveClientBuilder(String userName)
{
public TikTokLiveClientBuilder(String userName) {
this.clientSettings = LiveClientSettings.createDefault();
this.clientSettings.setHostName(userName);
this.tikTokEventHandler = new TikTokLiveEventHandler();
@@ -75,7 +72,6 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this;
}
public TikTokLiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure) {
onConfigure.accept(clientSettings);
return this;
@@ -88,44 +84,20 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
}
protected void validate() {
if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().isEmpty()) {
if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().isEmpty())
clientSettings.setClientLanguage("en");
}
if (clientSettings.getHostName() == null || clientSettings.getHostName().isEmpty()) {
if (clientSettings.getHostName() == null || clientSettings.getHostName().isEmpty())
throw new TikTokLiveException("HostName can not be null");
}
if (clientSettings.getHostName().startsWith("@")) {
if (clientSettings.getHostName().startsWith("@"))
clientSettings.setHostName(clientSettings.getHostName().substring(1));
}
var httpSettings = clientSettings.getHttpSettings();
httpSettings.getParams().put("app_language", clientSettings.getClientLanguage());
httpSettings.getParams().put("webcast_language", clientSettings.getClientLanguage());
this.logger = LoggerFactory.create(clientSettings.getHostName(), clientSettings);
var handler = new ConsoleHandler();
handler.setFormatter(new Formatter() {
@Override
public String format(LogRecord record) {
var sb = new StringBuilder();
sb.append(ConsoleColors.GREEN).append("[").append(record.getLoggerName()).append("] ");
sb.append(ConsoleColors.GREEN).append("[").append(record.getLevel()).append("]: ");
sb.append(ConsoleColors.WHITE_BRIGHT).append(record.getMessage());
sb.append(ConsoleColors.RESET).append("\n");
return sb.toString();
}
});
logger.setUseParentHandlers(false);
logger.addHandler(handler);
logger.setLevel(clientSettings.getLogLevel());
if (!clientSettings.isPrintToConsole()) {
logger.setLevel(Level.OFF);
}
}
public LiveClient build() {
@@ -266,13 +238,11 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this;
}
// @Override
public LiveClientBuilder onChest(EventConsumer<TikTokChestEvent> event) {
tikTokEventHandler.subscribe(TikTokChestEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicFanTicket(EventConsumer<TikTokLinkMicFanTicketEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicFanTicketEvent.class, event);
return this;
@@ -329,14 +299,12 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this;
}
@Override
public TikTokLiveClientBuilder onRoomInfo(EventConsumer<TikTokRoomInfoEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomInfoEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLivePaused(EventConsumer<TikTokLivePausedEvent> event) {
tikTokEventHandler.subscribe(TikTokLivePausedEvent.class, event);
return this;
@@ -363,7 +331,6 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this;
}
public TikTokLiveClientBuilder onGift(EventConsumer<TikTokGiftEvent> event) {
tikTokEventHandler.subscribe(TikTokGiftEvent.class, event);
return this;
@@ -374,7 +341,6 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this;
}
public TikTokLiveClientBuilder onLinkMicArmies(EventConsumer<TikTokLinkMicArmiesEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicArmiesEvent.class, event);
return this;
@@ -451,7 +417,6 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this;
}
public TikTokLiveClientBuilder onJoin(EventConsumer<TikTokJoinEvent> event) {
tikTokEventHandler.subscribe(TikTokJoinEvent.class, event);
return this;

View File

@@ -23,7 +23,7 @@
package io.github.jwdeveloper.tiktok;
import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.common.LoggerFactory;
import io.github.jwdeveloper.tiktok.common.*;
import io.github.jwdeveloper.tiktok.data.requests.*;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.*;
@@ -32,7 +32,6 @@ import io.github.jwdeveloper.tiktok.http.mappers.*;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import java.net.http.HttpResponse;
import java.util.Optional;
import java.util.logging.Logger;
public class TikTokLiveHttpClient implements LiveHttpClient
@@ -65,118 +64,98 @@ public class TikTokLiveHttpClient implements LiveHttpClient
this(new HttpClientFactory(LiveClientSettings.createDefault()), LiveClientSettings.createDefault());
}
public GiftsData.Response fetchGiftsData() {
var url = TIKTOK_URL_WEBCAST + "gift/list/";
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
var optional = httpFactory.client(url)
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to fetch gifts information's");
}
var json = optional.get();
return giftsDataMapper.map(json);
return getGiftsData();
} catch (TikTokProxyRequestException ignored) {}
}
}
var optional = httpFactory.client(url)
return getGiftsData();
}
public GiftsData.Response getGiftsData() {
var url = TIKTOK_URL_WEBCAST + "gift/list/";
var result = httpFactory.client(url)
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to fetch gifts information's");
}
if (result.isFailure())
throw new TikTokLiveRequestException("Unable to fetch gifts information's"+result.toStack());
var json = optional.get();
var json = result.getContent();
return giftsDataMapper.map(json);
}
@Override
public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) {
var url = TIKTOK_URL_WEB + "api-live/user/room";
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
var optional = httpFactory.client(url)
.withParam("uniqueId", request.getUserName())
.withParam("sourceType", "54")
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to get information's about user");
}
var json = optional.get();
return liveUserDataMapper.map(json);
return getLiveUserData(request);
} catch (TikTokProxyRequestException ignored) {}
}
}
var optional = httpFactory.client(url)
return getLiveUserData(request);
}
public LiveUserData.Response getLiveUserData(LiveUserData.Request request) {
var url = TIKTOK_URL_WEB + "api-live/user/room";
var result = httpFactory.client(url)
.withParam("uniqueId", request.getUserName())
.withParam("sourceType", "54")
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to get information's about user");
}
if (result.isFailure())
throw new TikTokLiveRequestException("Unable to get information's about user"+result.toStack());
var json = optional.get();
var json = result.getContent();
return liveUserDataMapper.map(json);
}
@Override
public LiveData.Response fetchLiveData(LiveData.Request request) {
var url = TIKTOK_URL_WEBCAST + "room/info";
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
var optional = httpFactory.client(url)
.withParam("room_id", request.getRoomId())
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to get info about live room");
}
var json = optional.get();
return liveDataMapper.map(json);
return getLiveData(request);
} catch (TikTokProxyRequestException ignored) {}
}
}
var optional = httpFactory.client(url)
return getLiveData(request);
}
public LiveData.Response getLiveData(LiveData.Request request) {
var url = TIKTOK_URL_WEBCAST + "room/info";
var result = httpFactory.client(url)
.withParam("room_id", request.getRoomId())
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to get info about live room");
}
if (result.isFailure())
throw new TikTokLiveRequestException("Unable to get info about live room"+result.toStack());
var json = optional.get();
var json = result.getContent();
return liveDataMapper.map(json);
}
@Override
public LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request) {
HttpResponse<byte[]> credentialsResponse = getOptionalProxyResponse(request).orElseGet(()-> getStarterPayload(request.getRoomId()));
var result = getStartingPayload(request);
HttpResponse<byte[]> credentialsResponse = result.getContent(); // Always guaranteed to have response
try {
var optionalHeader = credentialsResponse.headers().firstValue("x-set-tt-cookie");
if (optionalHeader.isEmpty()) {
logger.warning("SignServer Headers: "+credentialsResponse.headers().map());
throw new TikTokSignServerException("Sign server did not return the x-set-tt-cookie header");
var resultHeader = ActionResult.of(credentialsResponse.headers().firstValue("x-set-tt-cookie"));
if (resultHeader.isFailure()) {
logger.warning("SignServer Headers: "+request.getRoomId()+" - "+credentialsResponse.headers().map());
throw new TikTokSignServerException("Sign server did not return the x-set-tt-cookie header"+result.toStack());
}
var websocketCookie = optionalHeader.get();
var websocketCookie = resultHeader.getContent();
var webcastResponse = WebcastResponse.parseFrom(credentialsResponse.body());
var webSocketUrl = httpFactory
.client(webcastResponse.getPushServer())
@@ -190,11 +169,23 @@ public class TikTokLiveHttpClient implements LiveHttpClient
return new LiveConnectionData.Response(websocketCookie, webSocketUrl, webcastResponse);
} catch (InvalidProtocolBufferException e) {
throw new TikTokSignServerException("Unable to parse websocket credentials response to WebcastResponse");
throw new TikTokSignServerException("Unable to parse websocket credentials response to WebcastResponse"+result.toStack());
}
}
HttpResponse<byte[]> getStarterPayload(String room_id) {
private ActionResult<HttpResponse<byte[]>> getStartingPayload(LiveConnectionData.Request request) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
return getByteResponse(request.getRoomId());
} catch (TikTokProxyRequestException | TikTokSignServerException ignored) {}
}
}
return getByteResponse(request.getRoomId());
}
private ActionResult<HttpResponse<byte[]>> getByteResponse(String room_id) {
HttpClientBuilder builder = httpFactory.client(TIKTOK_SIGN_API)
.withParam("client", "ttlive-java")
.withParam("uuc", "1")
@@ -203,24 +194,11 @@ public class TikTokLiveHttpClient implements LiveHttpClient
if (clientSettings.getApiKey() != null)
builder.withParam("apiKey", clientSettings.getApiKey());
var optional = builder.build().toResponse();
var result = builder.build().toResponse();
if (optional.isEmpty()) {
throw new TikTokSignServerException("Unable to get websocket connection credentials");
}
return optional.get();
}
if (result.isFailure())
throw new TikTokSignServerException("Unable to get websocket connection credentials"+result.toStack());
Optional<HttpResponse<byte[]>> getOptionalProxyResponse(LiveConnectionData.Request request) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
HttpResponse<byte[]> credentialsResponse = getStarterPayload(request.getRoomId());
return Optional.of(credentialsResponse);
} catch (TikTokProxyRequestException | TikTokSignServerException ignored) {}
}
}
return Optional.empty();
return result;
}
}

View File

@@ -2,11 +2,12 @@ package io.github.jwdeveloper.tiktok.common;
import lombok.Data;
import java.util.Optional;
import java.util.function.Function;
@Data
public class ActionResult<T>
{
public class ActionResult<T> {
private boolean success = true;
private T content;
private String message;
@@ -29,6 +30,10 @@ public class ActionResult<T>
return new ActionResultBuilder<>(content);
}
public static <T> ActionResult<T> of(Optional<T> optional) {
return new ActionResult<>(optional.orElse(null), optional.isPresent());
}
public boolean isFailure() {
return !isSuccess();
}
@@ -36,48 +41,47 @@ public class ActionResult<T>
public boolean hasMessage() {
return message != null;
}
public String toStack() {
return hasMessage() ? " - "+message : "";
}
public boolean hasContent() {
return content != null;
}
public static <T> ActionResult<T> success() {
return new ActionResult<>(null, true);
}
public <Output> ActionResult<Output> cast(Output output) {
return new ActionResult<>(output, this.isSuccess(), this.getMessage());
}
public <Output> ActionResult<Output> cast() {
return new ActionResult<>(null, this.isSuccess(), this.getMessage());
return cast(null);
}
public <U> ActionResult<U> map(Function<? super T, ? extends U> mapper) {
return hasContent() ? cast(mapper.apply(content)) : cast();
}
public static <Input, Output> ActionResult<Output> cast(ActionResult<Input> action, Output output) {
return new ActionResult<>(output, action.isSuccess(), action.getMessage());
}
public static <T> ActionResult<T> success(T payload) {
return new ActionResult<>(payload, true);
}
public static <T> ActionResult<T> success(T payload, String message) {
return new ActionResult<>(payload, true, message);
}
public static <T> ActionResult<T> failure() {
return new ActionResult<>(null, false);
public static <T> ActionResult<T> success(T payload) {
return success(payload, null);
}
public static <T> ActionResult<T> failure(String message) {
return new ActionResult<>(null, false, message);
public static <T> ActionResult<T> success() {
return success(null);
}
public static <T> ActionResult<T> failure(T target, String message) {
return new ActionResult<>(target, false, message);
}
public static <T> ActionResult<T> failure(String message) {
return failure(null, message);
}
public static <T> ActionResult<T> failure() {
return failure(null);
}
}

View File

@@ -9,8 +9,10 @@ public class LoggerFactory
{
public static Logger create(String name, LiveClientSettings settings) {
Logger logger = Logger.getLogger(name);
if (logger.getHandlers().length == 0) {
var handler = new ConsoleHandler();
handler.setFormatter(new Formatter() {
handler.setFormatter(new Formatter()
{
@Override
public String format(LogRecord record) {
var sb = new StringBuilder();
@@ -24,7 +26,7 @@ public class LoggerFactory
logger.setUseParentHandlers(false);
logger.addHandler(handler);
logger.setLevel(settings.getLogLevel());
if (!settings.isPrintToConsole()) {
if (!settings.isPrintToConsole())
logger.setLevel(Level.OFF);
}
return logger;

View File

@@ -35,8 +35,8 @@ import java.util.regex.*;
import java.util.stream.Collectors;
@AllArgsConstructor
public class HttpClient
{
public class HttpClient {
protected final HttpClientSettings httpClientSettings;
protected final String url;
private final Pattern pattern = Pattern.compile("charset=(.*?)(?=&|$)");
@@ -71,13 +71,8 @@ public class HttpClient
}
}
public Optional<byte[]> toBinaryResponse() {
var optional = toResponse();
if (optional.isEmpty()) {
return Optional.empty();
}
var body = optional.get().body();
return Optional.of(body);
public ActionResult<byte[]> toBinaryResponse() {
return toResponse().map(HttpResponse::body);
}
public URI toUrl() {

View File

@@ -78,7 +78,8 @@ public class HttpClientBuilder {
}
public HttpClient build() {
if (httpClientSettings.getProxyClientSettings().isEnabled())
var proxyClientSettings = httpClientSettings.getProxyClientSettings();
if (proxyClientSettings.isEnabled() && proxyClientSettings.hasNext())
return new HttpProxyClient(httpClientSettings, url);
return new HttpClient(httpClientSettings, url);
}

View File

@@ -22,6 +22,7 @@
*/
package io.github.jwdeveloper.tiktok.http;
import io.github.jwdeveloper.tiktok.common.ActionResult;
import io.github.jwdeveloper.tiktok.data.settings.*;
import io.github.jwdeveloper.tiktok.exceptions.*;
@@ -35,8 +36,8 @@ import java.security.cert.X509Certificate;
import java.util.*;
import java.util.stream.Collectors;
public class HttpProxyClient extends HttpClient
{
public class HttpProxyClient extends HttpClient {
private final ProxyClientSettings proxySettings;
public HttpProxyClient(HttpClientSettings httpClientSettings, String url) {
@@ -44,14 +45,14 @@ public class HttpProxyClient extends HttpClient
this.proxySettings = httpClientSettings.getProxyClientSettings();
}
public Optional<HttpResponse<byte[]>> toResponse() {
public ActionResult<HttpResponse<byte[]>> toResponse() {
return switch (proxySettings.getType()) {
case HTTP, DIRECT -> handleHttpProxyRequest();
default -> handleSocksProxyRequest();
};
}
public Optional<HttpResponse<byte[]>> handleHttpProxyRequest() {
public ActionResult<HttpResponse<byte[]>> handleHttpProxyRequest() {
var builder = java.net.http.HttpClient.newBuilder()
.followRedirects(java.net.http.HttpClient.Redirect.NORMAL)
.cookieHandler(new CookieManager())
@@ -69,7 +70,7 @@ public class HttpProxyClient extends HttpClient
var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200)
continue;
return Optional.of(response);
return ActionResult.success(response);
} catch (HttpConnectTimeoutException | ConnectException e) {
if (proxySettings.isAutoDiscard())
proxySettings.remove();
@@ -85,7 +86,7 @@ public class HttpProxyClient extends HttpClient
throw new TikTokLiveRequestException("No more proxies available!");
}
private Optional<HttpResponse<byte[]>> handleSocksProxyRequest() {
private ActionResult<HttpResponse<byte[]>> handleSocksProxyRequest() {
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{ new X509TrustManager() {
@@ -117,7 +118,7 @@ public class HttpProxyClient extends HttpClient
var response = createHttpResponse(body, toUrl(), responseInfo);
return Optional.of(response);
return ActionResult.success(response);
} catch (IOException e) {
if (e.getMessage().contains("503") && proxySettings.isFallback()) // Indicates proxy protocol is not supported
return super.toResponse();
@@ -133,11 +134,10 @@ public class HttpProxyClient extends HttpClient
// Should never be reached!
System.out.println("handleSocksProxyRequest: If you see this, message us on discord!");
e.printStackTrace();
return Optional.empty();
} catch (TikTokLiveRequestException e) {
e.printStackTrace();
return Optional.empty();
}
return ActionResult.failure();
}
private ResponseInfo createResponseInfo(int code, Map<String, List<String>> headers) {

View File

@@ -31,6 +31,7 @@ import io.github.jwdeveloper.tiktok.extension.recorder.api.LiveRecorder;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.data.*;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.enums.LiveQuality;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.models.ConnectionState;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
@@ -79,9 +80,10 @@ public class RecorderListener implements LiveRecorder {
@TikTokEventObserver
private void onConnected(LiveClient liveClient, TikTokConnectedEvent event) {
if (isConnected())
return;
liveDownloadThread = new Thread(() -> {
try {
var bufferSize = 1024;
var url = new URL(downloadData.getFullUrl());
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
var headers = LiveClientSettings.DefaultRequestHeaders();
@@ -89,17 +91,25 @@ public class RecorderListener implements LiveRecorder {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
var in = new BufferedInputStream(connection.getInputStream());
var path = settings.getOutputPath() + File.separator + settings.getOutputFileName();
var file = new File(path);
file.getParentFile().mkdirs();
var fileOutputStream = new FileOutputStream(file);
byte[] dataBuffer = new byte[bufferSize];
file.createNewFile();
try (
var in = connection.getInputStream();
var fos = new FileOutputStream(file)
) {
byte[] dataBuffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(dataBuffer, 0, bufferSize)) != -1) {
fileOutputStream.write(dataBuffer, 0, bytesRead);
while (liveClient.getRoomInfo().getConnectionState() == ConnectionState.CONNECTED && (bytesRead = in.read(dataBuffer)) != -1) {
fos.write(dataBuffer, 0, bytesRead);
fos.flush();
}
} catch (IOException ignored) {
} finally {
liveClient.getLogger().severe("Stopped recording "+liveClient.getRoomInfo().getHostName());
}
in.close();
} catch (Exception e) {
e.printStackTrace();
}
@@ -108,27 +118,14 @@ public class RecorderListener implements LiveRecorder {
liveDownloadThread.start();
}
private static void downloadUsingStream(String urlStr, String file) throws IOException {
URL url = new URL(urlStr);
BufferedInputStream bis = new BufferedInputStream(url.openStream());
FileOutputStream fis = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int count;
while ((count = bis.read(buffer, 0, 1024)) != -1)
fis.write(buffer, 0, count);
fis.close();
bis.close();
}
@TikTokEventObserver
private void onDisconnected(LiveClient liveClient, TikTokDisconnectedEvent event) {
if (isConnected())
if (isConnected() && settings.isStopOnDisconnect())
liveDownloadThread.interrupt();
}
@TikTokEventObserver
private void onDisconnected(LiveClient liveClient, TikTokLiveEndedEvent event) {
private void onLiveEnded(LiveClient liveClient, TikTokLiveEndedEvent event) {
if (isConnected())
liveDownloadThread.interrupt();
}

View File

@@ -32,6 +32,7 @@ import java.util.function.Function;
@Getter
@Setter
public class RecorderSettings {
private String ffmpegPath;
private String quality;
private String format;
@@ -39,6 +40,7 @@ public class RecorderSettings {
private String outputFileName;
private Function<String,DownloadData> prepareDownloadData;
private boolean startOnConnected;
private boolean stopOnDisconnect = true;
public static RecorderSettings DEFAULT() {
return new RecorderSettings();