206 lines
6.9 KiB
Java
206 lines
6.9 KiB
Java
package ctbrec.sites.dreamcam;
|
|
|
|
import com.iheartradio.m3u8.*;
|
|
import com.iheartradio.m3u8.data.MasterPlaylist;
|
|
import com.iheartradio.m3u8.data.Playlist;
|
|
import com.iheartradio.m3u8.data.PlaylistData;
|
|
import ctbrec.AbstractModel;
|
|
import ctbrec.Config;
|
|
import ctbrec.StringUtil;
|
|
import ctbrec.io.HttpClient;
|
|
import ctbrec.io.HttpException;
|
|
import ctbrec.recorder.InvalidPlaylistException;
|
|
import ctbrec.recorder.download.Download;
|
|
import ctbrec.recorder.download.StreamSource;
|
|
import ctbrec.recorder.download.hls.MergedFfmpegHlsDownload;
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.text.MessageFormat;
|
|
import java.time.Duration;
|
|
import java.time.Instant;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.concurrent.ExecutionException;
|
|
import javax.xml.bind.JAXBException;
|
|
import okhttp3.FormBody;
|
|
import okhttp3.Request;
|
|
import okhttp3.Response;
|
|
import org.json.JSONArray;
|
|
import org.json.JSONObject;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import static ctbrec.Model.State.*;
|
|
import static ctbrec.io.HttpConstants.*;
|
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
|
|
public class DreamcamModel extends AbstractModel {
|
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(DreamcamModel.class);
|
|
private static final String API_URL = "https://bss.dreamcamtrue.com";
|
|
private int[] resolution = new int[2];
|
|
private JSONObject modelInfo;
|
|
private boolean VRMode = false;
|
|
|
|
private transient Instant lastInfoRequest = Instant.EPOCH;
|
|
|
|
@Override
|
|
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
|
|
if (ignoreCache) {
|
|
try {
|
|
JSONObject json = getModelInfo();
|
|
mapOnlineState(json.optString("broadcastStatus"));
|
|
} catch (Exception e) {
|
|
setOnlineState(OFFLINE);
|
|
}
|
|
}
|
|
return onlineState == ONLINE;
|
|
}
|
|
|
|
private void mapOnlineState(String status) {
|
|
switch (status) {
|
|
case "public" -> setOnlineState(ONLINE);
|
|
case "private" -> setOnlineState(PRIVATE);
|
|
case "offline" -> setOnlineState(OFFLINE);
|
|
default -> setOnlineState(OFFLINE);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public State getOnlineState(boolean failFast) throws IOException, ExecutionException {
|
|
if (failFast && onlineState != UNKNOWN) {
|
|
return onlineState;
|
|
} else {
|
|
try {
|
|
JSONObject json = getModelInfo();
|
|
mapOnlineState(json.optString("broadcastStatus"));
|
|
} catch (Exception ex) {
|
|
setOnlineState(OFFLINE);
|
|
}
|
|
return onlineState;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public List<StreamSource> getStreamSources() throws InvalidPlaylistException {
|
|
List<StreamSource> sources = new ArrayList<>();
|
|
try {
|
|
StreamSource src = new StreamSource();
|
|
src.mediaPlaylistUrl = getPlaylistUrl();
|
|
sources.add(src);
|
|
} catch (Exception e) {
|
|
LOG.error("Can not get stream sources for {}: {}", getName(), e.getMessage());
|
|
throw new InvalidPlaylistException(e.getMessage());
|
|
}
|
|
return sources;
|
|
}
|
|
|
|
private String getPlaylistUrl() throws IOException, InvalidPlaylistException {
|
|
JSONObject json = getModelInfo();
|
|
String mediaUrl = "";
|
|
if (json.has("streams")) {
|
|
JSONArray streams = json.getJSONArray("streams");
|
|
for (int i=0; i < streams.length(); i++) {
|
|
JSONObject s = streams.getJSONObject(i);
|
|
if (s.has("streamType") && s.has("url")) {
|
|
String streamType = s.getString("streamType");
|
|
if (streamType.equals("video2D")) {
|
|
mediaUrl = s.optString("url");
|
|
LOG.trace("PlaylistUrl for {}: {}", getName(), mediaUrl);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (StringUtil.isBlank(mediaUrl)) {
|
|
throw new InvalidPlaylistException("Playlist has no media");
|
|
}
|
|
return mediaUrl;
|
|
}
|
|
|
|
public String getWsUrl() throws IOException {
|
|
JSONObject json = getModelInfo();
|
|
return json.optString("streamUrl").replace("fmp4s://", "wss://");
|
|
}
|
|
|
|
public String getChatId() throws IOException {
|
|
JSONObject json = getModelInfo();
|
|
return json.optString("roomChatId");
|
|
}
|
|
|
|
@Override
|
|
public int[] getStreamResolution(boolean failFast) throws ExecutionException {
|
|
return new int[2];
|
|
}
|
|
|
|
private JSONObject getModelInfo() throws IOException {
|
|
if (Duration.between(lastInfoRequest, Instant.now()).getSeconds() < 5) {
|
|
modelInfo = Optional.ofNullable(modelInfo).orElse(loadModelInfo());
|
|
} else {
|
|
modelInfo = loadModelInfo();
|
|
}
|
|
return modelInfo;
|
|
}
|
|
|
|
private JSONObject loadModelInfo() throws IOException {
|
|
lastInfoRequest = Instant.now();
|
|
String url = MessageFormat.format(API_URL + "/api/clients/v1/broadcasts/models/{0}?partnerId=dreamcam_oauth2&show-hidden=true&stream-types=video2D,video3D", getName());
|
|
Request req = new Request.Builder()
|
|
.url(url)
|
|
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
|
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
|
|
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
|
|
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
|
.header(REFERER, getUrl())
|
|
.header(ORIGIN, getSite().getBaseUrl())
|
|
.build();
|
|
try (Response response = getSite().getHttpClient().execute(req)) {
|
|
if (response.isSuccessful()) {
|
|
JSONObject json = new JSONObject(response.body().string());
|
|
return json;
|
|
} else {
|
|
throw new HttpException(response.code(), response.message());
|
|
}
|
|
}
|
|
}
|
|
|
|
public String getPreviewURL() throws IOException {
|
|
JSONObject json = getModelInfo();
|
|
return json.optString("modelProfilePhotoUrl");
|
|
}
|
|
|
|
@Override
|
|
public boolean follow() throws IOException {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean unfollow() throws IOException {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void receiveTip(Double tokens) throws IOException {
|
|
// not implemented
|
|
}
|
|
|
|
@Override
|
|
public void invalidateCacheEntries() {
|
|
resolution = new int[]{0, 0};
|
|
lastInfoRequest = Instant.EPOCH;
|
|
modelInfo = null;
|
|
}
|
|
|
|
@Override
|
|
public Download createDownload() {
|
|
if (Config.getInstance().getSettings().dreamcamVR) {
|
|
return new DreamcamDownload(getSite().getHttpClient());
|
|
} else {
|
|
return new MergedFfmpegHlsDownload(getSite().getHttpClient());
|
|
}
|
|
}
|
|
}
|